diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7f5c6583..298b0b34 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -51,16 +51,23 @@ jobs: if: env.GIT_DIFF run: | go test -tags=test -coverprofile=profile-wasmbinding.txt -covermode=atomic ./wasmbinding/... + - name: rewards coverage (test build tag) + if: env.GIT_DIFF + run: | + go test -tags=test -coverprofile=profile-rewards.txt -covermode=atomic ./x/rewards/... - uses: actions/upload-artifact@v4 if: env.GIT_DIFF with: name: "${{ github.sha }}-coverage" - path: ./profile.txt + path: | + ./profile.txt + ./profile-wasmbinding.txt + ./profile-rewards.txt - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 if: env.GIT_DIFF with: - files: ./profile.txt,./profile-wasmbinding.txt + files: ./profile.txt,./profile-wasmbinding.txt,./profile-rewards.txt token: ${{ secrets.CODECOV_TOKEN }} repo-analysis: diff --git a/CHANGELOG.md b/CHANGELOG.md index b9f07f8d..05c2163b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,31 +2,54 @@ ## Unreleased +### Added + +- Replace timed linear `ReleaseSchedule` emissions in `x/rewards` with continuous inflation-based utility rewards driven by bonded ratio: `inflation = clamp((1 - bonded/goal) × rate_change × bonded, min, max)`, amount = `inflation × supply_base × Δt / year`, capped at remaining pool balance (emits until pool runs dry). Adds staking `BondedRatio` dependency and gov params `goal_bonded`, `inflation_min`, `inflation_max`, `inflation_rate_change` (default `0.13`), and `supply_base` (default `0` disables emissions; notional emission scale, not chain total supply). Enable via `MsgFundPool` + gov `MsgUpdateParams`. Module consensus version bumped to `2` with a store migration that deletes obsolete `ReleaseSchedule` state and backfills default inflation params +- Emit `update_params`, `fund_pool`, and `reward_distributed` events from x/rewards (`reward_distributed` now includes `inflation_rate` and `bonded_ratio`) + +### Removed + +- Remove `ReleaseSchedule`, `MsgChangeSchedule`, and the release-schedule query/CLI from `x/rewards`; emissions are continuous while `supply_base > 0` and the pool has funds (`last_release_time` / `total_released` live on `RewardPool`) + +## v7.3.1 - 2026-08-06 + +### Fixed + +- Keep the gov module account on the bank blocked list so EVM BalanceHandler does not mirror gov deposits into StateDB (fixes Safe → gov precompile `deposit` failing on commit with `unauthorized`) ([#368](https://github.com/KiiChain/kiichain/pull/368)) + +## v7.3.0 - 2026-07-28 + ### Dependencies - [EVM](https://github.com/KiiChain/evm) fork from v0.6.0-fork.1 to [v0.6.0-fork.2](https://github.com/KiiChain/evm/releases/tag/v0.6.0-fork.2): bounded internal EVM call gas limit, EVM fee refunds, distribution precompile 32-byte withdraw fix, and CosmWasm EVM query undercharge fix - [EVM](https://github.com/KiiChain/evm) fork bump from `v0.6.0-fork.2` to [v0.6.1-fork.1](https://github.com/KiiChain/evm/releases/tag/v0.6.1-fork.1), applied via the coordinated `v7.3.0` upgrade: the July 2026 Cosmos EVM hotfix (precompile gas accounting alignment and StateDB locked-balance snapshotting), published upstream as [cosmos/evm v0.6.1](https://github.com/cosmos/evm/releases/tag/v0.6.1) -### Added - -- Emit `update_params`, `fund_pool`, `change_schedule`, and `reward_distributed` events from x/rewards -- Emit `update_params` and `set_denom_metadata` events from tokenfactory -- Emit `update_params`, `update_fee_tokens`, `module_disabled` and `token_disabled` events on fee abstraction -- Emit `update_params` event on oracle module - ### Fixed - Close an expedited-governance whitelist bypass in `GovExpeditedProposalsDecorator` where the check only inspected top-level messages: a non-whitelisted `MsgSubmitProposal` wrapped in `authz.MsgExec` could enter the expedited voting path. The decorator now recurses into `authz.MsgExec` (including nested execs) and applies the expedited whitelist validation to wrapped proposals - Compute the oracle ballot `StandardDeviation` as a stake-weighted variance (weight each squared deviation by the vote's power and divide by total voting power) instead of an unweighted average divided by the vote count, aligning the reward-band width with the stake-weighted median and preventing a group of low-stake validators from inflating the deviation to widen the accepted vote window - Close an oracle slashing bypass in the `EndBlocker` where validators were scored against the post-filtered `voteTargets` map: a denom that received votes but was pushed below the vote threshold (e.g. by a coordinated group abstaining) was dropped from the scoring denominator, letting the abstainers avoid miss penalties. Participation is now scored against the configured targets that received votes (passing targets plus below-threshold targets), crediting validators that voted on a below-threshold target while counting abstention on it as a miss; targets that received no votes at all are still excluded so a legitimately unpriceable denom cannot mass-slash the validator set - Allow EIP-7702 delegated EOAs to send direct EVM transactions by exempting delegation-designator code from the externally-owned-account-only check in `VerifyIfAccountExists`, so accounts that delegate via `SetCodeTx` can still manage (and revoke) their own delegation without a sponsored transaction -- Remove the forced minimum 1-unit-per-block reward release in `CalculateReward` and skip (instead of deactivating) sub-unit blocks in the rewards `BeginBlocker`, so the proportional share accumulates and the pool follows the configured schedule independent of block time (previously a 10-year, 1M-unit schedule drained in ~12 days at the 1s target block time and ~28 days at the current ~2.4s rate, regardless of the configured duration) - Reject `MsgEthereumTx` from being dispatched through the authz keeper (including when nested inside `authz.MsgExec`), closing an EVM ante bypass on message-router execution paths that skip the ante handler - Fix feegrant denomination bypass in the cosmos fee ante handler by converting the fee before consuming the grant, so `UseGrantedFees` is checked against the same coins later deducted (prevents a grantee from forcing the granter to pay in a non-granted fee-abstraction denom) +- Bound tokenfactory denom metadata size (`MaxDenomMetadataSize`) in `MsgSetDenomMetadata.ValidateBasic` and `msgServer.SetDenomMetadata` to prevent oversized metadata rewrites (including via the CosmWasm binding) from forcing unbounded native store writes that overrun the transaction's declared gas +- Fix native token supply inflation from the stateful precompiles by wrapping the account address codec (`evmAddressCodec`) to reject non-20-byte accounts (e.g. a 32-byte bech32 withdraw, module, or CosmWasm contract address) at decode time, preventing such addresses from being truncated and minted a duplicate balance when mirrored into the EVM StateDB +- Close governance vote minimum-stake bypass in `GovVoteDecorator` by enforcing the stake check on `MsgVoteWeighted` (`govv1` and `govv1beta1`) and recursing into nested `authz.MsgExec` messages so wrapped votes can no longer skip the requirement +- Add a `ValidateModuleAccounting` check (rewards module bank balance must cover the `CommunityPool`) and run it at genesis to surface accounting/bank divergences early + +## v7.2.0 - 2026-04-16 + +### Added + +- Emit `update_params` and `set_denom_metadata` events from tokenfactory +- Emit `update_params`, `update_fee_tokens`, `module_disabled` and `token_disabled` events on fee abstraction +- Emit `update_params` event on oracle module + +### Fixed + - Refactor `PerformSetMetadata` in wasmbinding to delegate to `msgServer.SetDenomMetadata`, ensuring the `EnableSetMetadata` capability check is enforced - Ensure that `UpdateTokenMetadata.Decimals` matches the ERC20 or bank records - Fixed odd validation on tokenfactory change admin that blocked removing admin from the token -- Fix division-by-zero chain halt in `CalculateReward` caused by sub-second schedule durations; replace `Seconds()` truncation with `Nanoseconds()` precision and release full remaining reward when `EndTime <= LastReleaseTime` ([#267](https://github.com/KiiChain/kiichain/issues/267)) - Add denom string length validation (max 128 bytes) to oracle precompile and query server to prevent memory exhaustion via oversized inputs - Add result limits to oracle list queries (ExchangeRates, Actives, VoteTargets capped at 1000; PriceSnapshotHistory capped at 500) to prevent unbounded iteration - Fix NewClaim constructor assigning power to Weight field instead of the weight parameter (x/oracle/types/ballot.go) @@ -39,13 +62,8 @@ - Ensure feeTokenMetadata initial prices after updateFeeTokenMetadata is picked up from oracle - Use `DecCoins.Validate()` on `RewardPool.ValidateGenesis` to catch malformed denom formats, duplicate denoms, bad ordering - Enforce denom consistency in `GenesisState.Validate` with `Params.TokenDenom` -- Bound tokenfactory denom metadata size (`MaxDenomMetadataSize`) in `MsgSetDenomMetadata.ValidateBasic` and `msgServer.SetDenomMetadata` to prevent oversized metadata rewrites (including via the CosmWasm binding) from forcing unbounded native store writes that overrun the transaction's declared gas - Limited tokenfactory queries, removing denial of service possibility - Indexed admins to reduce query space on tokenfactory denom queries -- Fix native token supply inflation from the stateful precompiles by wrapping the account address codec (`evmAddressCodec`) to reject non-20-byte accounts (e.g. a 32-byte bech32 withdraw, module, or CosmWasm contract address) at decode time, preventing such addresses from being truncated and minted a duplicate balance when mirrored into the EVM StateDB -- Close governance vote minimum-stake bypass in `GovVoteDecorator` by enforcing the stake check on `MsgVoteWeighted` (`govv1` and `govv1beta1`) and recursing into nested `authz.MsgExec` messages so wrapped votes can no longer skip the requirement -- Prevent a chain halt in the rewards `BeginBlocker` by routing `SendCoinsFromModuleToModule` failures through `haltSchedule` (graceful schedule deactivation) instead of returning a fatal error, matching the other reward release error paths -- Add a `ValidateModuleAccounting` check (rewards module bank balance must cover the `CommunityPool`) and run it at genesis to surface accounting/bank divergences early ### Removed diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index cd21f691..9ffae1d4 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -389,6 +389,7 @@ func NewAppKeeper( appCodec, runtime.NewKVStoreService(appKeepers.keys[rewardstypes.StoreKey]), appKeepers.BankKeeper, + appKeepers.StakingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), authtypes.FeeCollectorName, ) diff --git a/contrib/scripts/test_rewards_manual.sh b/contrib/scripts/test_rewards_manual.sh new file mode 100755 index 00000000..38ab819f --- /dev/null +++ b/contrib/scripts/test_rewards_manual.sh @@ -0,0 +1,298 @@ +#!/usr/bin/env bash +# Manual smoke test for inflation-based x/rewards emissions. +# +# Flow: +# 1. Build + init a single-validator localnet (short gov voting period) +# 2. Fund the rewards pool +# 3. Pass MsgUpdateParams with supply_base > 0 +# 4. Wait a few blocks and assert pool decreased / total_released increased +# +# Usage: +# ./contrib/scripts/test_rewards_manual.sh +# KEEP_HOME=1 ./contrib/scripts/test_rewards_manual.sh # leave node home for inspection +# +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +BIN="${BIN:-$ROOT_DIR/build/kiichaind}" +HOME_DIR="${HOME_DIR:-$HOME/.kiichaind-rewards-manual}" +CHAIN_ID="${CHAIN_ID:-localchain_1010-1}" +KEY="${KEY:-val}" +DENOM="akii" +RPC_URL="http://127.0.0.1:26657" +NODE="tcp://127.0.0.1:26657" +# Standard gov module account (bech32 "kii" prefix) +GOV_AUTHORITY="kii10d07y265gmmuvt4z0w9aw880jnsr700jrff0qv" + +FUND_AMOUNT="1000000000000000000000${DENOM}" # 1000 kii +SUPPLY_BASE="1000000000000000000000000" # 1e24 +DEPOSIT_AMOUNT="10000000${DENOM}" +GAS_PRICES="3000000000${DENOM}" + +log() { printf '\n==> %s\n' "$*"; } +die() { printf 'ERROR: %s\n' "$*" >&2; exit 1; } + +broadcast_tx() { + local out code txhash + out="$("$@" --broadcast-mode sync -o json)" + code="$(echo "$out" | jq -r '.code // 0')" + txhash="$(echo "$out" | jq -r '.txhash // empty')" + if [[ "$code" != "0" ]]; then + echo "$out" | jq . >&2 || echo "$out" >&2 + die "tx rejected at broadcast (code=$code)" + fi + [[ -n "$txhash" ]] || die "missing txhash from broadcast" + # Wait for inclusion + local i status + for i in $(seq 1 30); do + if status="$("$BIN" q tx "$txhash" --node "$NODE" -o json 2>/dev/null)"; then + code="$(echo "$status" | jq -r '.code // 0')" + if [[ "$code" != "0" ]]; then + echo "$status" | jq . >&2 + die "tx $txhash failed on-chain (code=$code)" + fi + return 0 + fi + sleep 1 + done + die "timed out waiting for tx $txhash" +} + +cleanup() { + if [[ -n "${NODE_PID:-}" ]] && kill -0 "$NODE_PID" 2>/dev/null; then + log "Stopping local node (pid $NODE_PID)" + kill "$NODE_PID" 2>/dev/null || true + wait "$NODE_PID" 2>/dev/null || true + fi + if [[ "${KEEP_HOME:-0}" != "1" ]]; then + rm -rf "$HOME_DIR" + else + log "Keeping home dir at $HOME_DIR (KEEP_HOME=1)" + fi +} +trap cleanup EXIT + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || die "missing required command: $1" +} + +need_cmd jq +need_cmd curl +need_cmd python3 + +log "Building kiichaind" +make build >/dev/null +[[ -x "$BIN" ]] || die "binary not found at $BIN" + +# Free ports if a previous run left a node up +pkill -f "kiichaind.*${HOME_DIR}" >/dev/null 2>&1 || true +sleep 1 +rm -rf "$HOME_DIR" + +log "Initializing chain at $HOME_DIR" +"$BIN" init rewards-manual --chain-id "$CHAIN_ID" --home "$HOME_DIR" >/dev/null 2>&1 +"$BIN" config set client chain-id "$CHAIN_ID" --home "$HOME_DIR" +"$BIN" config set client keyring-backend test --home "$HOME_DIR" +"$BIN" config set client node "$NODE" --home "$HOME_DIR" + +"$BIN" keys add "$KEY" --home "$HOME_DIR" --keyring-backend test >/dev/null 2>&1 +"$BIN" genesis add-genesis-account "$KEY" \ + "10000000000000000000000000000000000000${DENOM}" \ + --home "$HOME_DIR" --keyring-backend test >/dev/null + +"$BIN" genesis gentx "$KEY" "1000000000000000000000${DENOM}" \ + --home "$HOME_DIR" --chain-id "$CHAIN_ID" --keyring-backend test >/dev/null 2>&1 +"$BIN" genesis collect-gentxs --home "$HOME_DIR" >/dev/null 2>&1 + +GENESIS="$HOME_DIR/config/genesis.json" +APP_TOML="$HOME_DIR/config/app.toml" + +# Short gov window + matching EVM denom / metadata (same as start-localnet-ci) +tmp="$(mktemp)" +jq ' + .app_state.gov.params.voting_period = "12s" + | .app_state.gov.params.expedited_voting_period = "8s" + | .app_state.gov.params.min_deposit = [{"denom":"akii","amount":"10000000"}] + | .app_state.gov.params.expedited_min_deposit = [{"denom":"akii","amount":"10000000"}] + | .app_state.gov.params.quorum = "0.000000000000000001" + | .app_state.gov.params.threshold = "0.000000000000000001" + | .app_state.evm.params.evm_denom = "akii" + | .app_state.bank.denom_metadata = [{ + "description":"The native staking token of the kiichain network", + "denom_units":[{"denom":"akii","exponent":0},{"denom":"kii","exponent":18}], + "base":"akii","display":"kii","name":"kii","symbol":"KII" + }] +' "$GENESIS" > "$tmp" && mv "$tmp" "$GENESIS" + +if [[ "$(uname)" == "Darwin" ]]; then + sed -i.bak 's/minimum-gas-prices = ""/minimum-gas-prices = "0akii"/' "$APP_TOML" +else + sed -i 's/minimum-gas-prices = ""/minimum-gas-prices = "0akii"/' "$APP_TOML" +fi +# Keep feemarket from blocking with a high floor relative to our gas prices +tmp="$(mktemp)" +jq ' + .app_state.feemarket.params.no_base_fee = true + | .app_state.feemarket.params.min_gas_price = "0.000000000000000000" + | .app_state.feemarket.params.base_fee = "0.000000000000000000" +' "$GENESIS" > "$tmp" && mv "$tmp" "$GENESIS" + +log "Starting node" +"$BIN" start --home "$HOME_DIR" >"$HOME_DIR/node.log" 2>&1 & +NODE_PID=$! + +# Wait for RPC +for i in $(seq 1 60); do + if curl -sf "$RPC_URL/status" >/dev/null 2>&1; then + break + fi + sleep 1 + if [[ $i -eq 60 ]]; then + tail -n 80 "$HOME_DIR/node.log" || true + die "node did not become ready" + fi +done +# Wait until height > 1 +for i in $(seq 1 30); do + height="$(curl -sf "$RPC_URL/status" | jq -r '.result.sync_info.latest_block_height')" + if [[ "$height" =~ ^[0-9]+$ ]] && (( height > 1 )); then + log "Node ready at height $height" + break + fi + sleep 1 + if [[ $i -eq 30 ]]; then + die "node never reached height > 1" + fi +done + +TX_FLAGS=( + --home "$HOME_DIR" + --keyring-backend test + --chain-id "$CHAIN_ID" + --node "$NODE" + --gas 5000000 + --gas-prices "$GAS_PRICES" + --yes +) + +log "Query initial rewards params / pool" +"$BIN" q rewards params --node "$NODE" -o json | jq . +INITIAL_POOL="$("$BIN" q rewards reward-pool --node "$NODE" -o json)" +echo "$INITIAL_POOL" | jq . +INITIAL_SUPPLY_BASE="$( + "$BIN" q rewards params --node "$NODE" -o json | jq -r '.params.supply_base // .supply_base // "0"' +)" +[[ "$INITIAL_SUPPLY_BASE" == "0" || "$INITIAL_SUPPLY_BASE" == "0.000000000000000000" ]] \ + || die "expected default supply_base=0, got $INITIAL_SUPPLY_BASE" + +log "Fund rewards pool with $FUND_AMOUNT" +broadcast_tx "$BIN" tx rewards fund-pool "$FUND_AMOUNT" --from "$KEY" "${TX_FLAGS[@]}" + +FUNDED_POOL="$("$BIN" q rewards reward-pool --node "$NODE" -o json)" +echo "$FUNDED_POOL" | jq . +FUNDED_AMOUNT="$(echo "$FUNDED_POOL" | jq -r ' + (.reward_pool.community_pool // .community_pool // []) + | map(select(.denom=="akii")) | .[0].amount // "0" +')" +[[ "$FUNDED_AMOUNT" != "0" && "$FUNDED_AMOUNT" != "null" ]] || die "pool still empty after fund-pool" +log "Pool funded amount=$FUNDED_AMOUNT" + +PROPOSAL_FILE="$HOME_DIR/proposal_update_rewards_params.json" +cat >"$PROPOSAL_FILE" < int: + s = s.strip() + if "." in s: + s = s.split(".", 1)[0] + return int(s) + +funded = to_int(sys.argv[1]) +final = to_int(sys.argv[2]) +released = to_int(sys.argv[3]) +if not (final < funded): + raise SystemExit(f"pool did not decrease: funded={funded} final={final}") +if not (released > 0): + raise SystemExit(f"total_released not positive: {released}") +print(f"OK: pool decreased by {funded - final}, total_released={released}") +PY + +log "Manual rewards smoke test PASSED" diff --git a/proto/kiichain/rewards/v1beta1/genesis.proto b/proto/kiichain/rewards/v1beta1/genesis.proto index 76ed9089..4c707fb3 100755 --- a/proto/kiichain/rewards/v1beta1/genesis.proto +++ b/proto/kiichain/rewards/v1beta1/genesis.proto @@ -2,7 +2,6 @@ syntax = "proto3"; package kiichain.rewards.v1beta1; import "gogoproto/gogo.proto"; -import "cosmos/base/v1beta1/coin.proto"; import "kiichain/rewards/v1beta1/params.proto"; import "kiichain/rewards/v1beta1/types.proto"; @@ -13,9 +12,10 @@ message GenesisState { // params defines the parameters of the module. Params params = 1 [ (gogoproto.nullable) = false ]; - // release_schedule has information of how the reward is being released - ReleaseSchedule release_schedule = 2 [ (gogoproto.nullable) = false ]; + // Reserved: previously release_schedule + reserved 2; + reserved "release_schedule"; // reward_pool has information on the community pool RewardPool reward_pool = 3 [ (gogoproto.nullable) = false ]; -} \ No newline at end of file +} diff --git a/proto/kiichain/rewards/v1beta1/params.proto b/proto/kiichain/rewards/v1beta1/params.proto index fed3d20b..942a957b 100644 --- a/proto/kiichain/rewards/v1beta1/params.proto +++ b/proto/kiichain/rewards/v1beta1/params.proto @@ -2,12 +2,43 @@ syntax = "proto3"; package kiichain.rewards.v1beta1; import "gogoproto/gogo.proto"; -import "kiichain/rewards/v1beta1/types.proto"; option go_package = "github.com/kiichain/kiichain/x/rewards/types"; // Params defines the parameters for the rewards module. message Params { - // Denom used + // Denom used for emissions string token_denom = 1; -} \ No newline at end of file + + // Target fraction of supply staked (e.g. "0.67") + string goal_bonded = 2 [ + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + // Floor on the emission rate (e.g. "0.00") + string inflation_min = 3 [ + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + // Ceiling on the emission rate (e.g. "0.20") + string inflation_max = 4 [ + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + // Notional base (in token base units) used to size annual provisions: + // annual_provision = inflation * supply_base. This is NOT chain total supply; + // it is a governance knob for emission scale. Zero disables emissions. + string supply_base = 5 [ + (gogoproto.customtype) = "cosmossdk.io/math.Int", + (gogoproto.nullable) = false + ]; + + // Steepness of the emission curve (e.g. "0.13") + string inflation_rate_change = 6 [ + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; +} diff --git a/proto/kiichain/rewards/v1beta1/query.proto b/proto/kiichain/rewards/v1beta1/query.proto index 62ff4061..a0d017c2 100644 --- a/proto/kiichain/rewards/v1beta1/query.proto +++ b/proto/kiichain/rewards/v1beta1/query.proto @@ -3,7 +3,6 @@ package kiichain.rewards.v1beta1; import "gogoproto/gogo.proto"; import "google/api/annotations.proto"; -import "cosmos/base/query/v1beta1/pagination.proto"; import "kiichain/rewards/v1beta1/types.proto"; import "kiichain/rewards/v1beta1/params.proto"; @@ -17,14 +16,6 @@ service Query { option (google.api.http).get = "/kiichain/rewards/v1beta1/params"; } - // ReleaseSchedule defines a gRPC query method for fetching - // ReleaseSchedule data. - rpc ReleaseSchedule(QueryReleaseScheduleRequest) - returns (QueryReleaseScheduleResponse) { - option (google.api.http).get = - "/kiichain/rewards/v1beta1/release-schedule"; - } - // RewardPool defines a gRPC query method for fetching // RewardPool data. rpc RewardPool(QueryRewardPoolRequest) @@ -43,19 +34,6 @@ message QueryParamsResponse { Params params = 1 [ (gogoproto.nullable) = false ]; } -// QueryReleaseScheduleRequest defines the request structure for the -// ReleaseSchedule gRPC query. -message QueryReleaseScheduleRequest {} - -// QueryReleaseScheduleResponse defines the response structure for the -// ReleaseSchedule gRPC query. -message QueryReleaseScheduleResponse { - ReleaseSchedule release_schedule = 1 [ - (gogoproto.moretags) = "yaml:\"release_schedule\"", - (gogoproto.nullable) = false - ]; -} - // QueryRewardPoolRequest defines the request structure for the // RewardPool gRPC query. message QueryRewardPoolRequest {} diff --git a/proto/kiichain/rewards/v1beta1/tx.proto b/proto/kiichain/rewards/v1beta1/tx.proto index 1ee7b7aa..d3ea8615 100644 --- a/proto/kiichain/rewards/v1beta1/tx.proto +++ b/proto/kiichain/rewards/v1beta1/tx.proto @@ -4,19 +4,17 @@ package kiichain.rewards.v1beta1; import "gogoproto/gogo.proto"; import "cosmos/base/v1beta1/coin.proto"; import "kiichain/rewards/v1beta1/params.proto"; -import "kiichain/rewards/v1beta1/types.proto"; -import "google/protobuf/timestamp.proto"; import "cosmos/msg/v1/msg.proto"; import "cosmos_proto/cosmos.proto"; import "amino/amino.proto"; option go_package = "github.com/kiichain/kiichain/x/rewards/types"; -// Msg defines the tokefactory module's gRPC message service. +// Msg defines the rewards module's gRPC message service. service Msg { option (cosmos.msg.v1.service) = true; - // FundPool adds funds to the community pool that can be used on a extension + // FundPool adds funds to the community pool rpc FundPool(MsgFundPool) returns (MsgFundPoolResponse); // UpdateParams defines a governance operation for updating the x/rewards module @@ -24,10 +22,6 @@ service Msg { // // Since: cosmos-sdk 0.47 rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); - - // ChangeSchedule defines a governance operation for changing the reward and - // its schedule - rpc ChangeSchedule(MsgChangeSchedule) returns (MsgChangeScheduleResponse); } // MsgFundPool is the sdk.Msg type for funding the community pool @@ -67,19 +61,3 @@ message MsgUpdateParams { // // Since: cosmos-sdk 0.47 message MsgUpdateParamsResponse {} - -// MsgChangeSchedule is the Msg/ChangeSchedule request type. -message MsgChangeSchedule { - option (cosmos.msg.v1.signer) = "authority"; - option (amino.name) = "rewards/change-schedule"; - - // authority is the address of the governance account. - string authority = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; - - // New information for the schedule - ReleaseSchedule schedule = 2 [ (gogoproto.nullable) = false ]; -} - -// MsgChangeScheduleResponse defines the response structure for executing a -// MsgChangeSchedule message. -message MsgChangeScheduleResponse {} diff --git a/proto/kiichain/rewards/v1beta1/types.proto b/proto/kiichain/rewards/v1beta1/types.proto index 106468c1..724e7b8f 100644 --- a/proto/kiichain/rewards/v1beta1/types.proto +++ b/proto/kiichain/rewards/v1beta1/types.proto @@ -8,36 +8,6 @@ import "amino/amino.proto"; option go_package = "github.com/kiichain/kiichain/x/rewards/types"; -// ReleaseSchedule defines information related to reward distribution -message ReleaseSchedule { - // Total amount to be rewarded - cosmos.base.v1beta1.Coin total_amount = 1 [ - (gogoproto.nullable) = false, - (gogoproto.moretags) = "yaml:\"total_amount\"" - ]; - // Amount released - cosmos.base.v1beta1.Coin released_amount = 2 [ - (gogoproto.nullable) = false, // can start empty - (gogoproto.moretags) = "yaml:\"released_amount\"" - ]; - // Timestamp of end of release - google.protobuf.Timestamp end_time = 3 [ - (gogoproto.stdtime) = true, - (gogoproto.nullable) = false, - (gogoproto.moretags) = "yaml:\"end_time\"" - ]; - // Last height released - google.protobuf.Timestamp last_release_time = 5 [ - (gogoproto.stdtime) = true, - (gogoproto.nullable) = false, - (gogoproto.moretags) = "yaml:\"last_release_time\"" - ]; - // If reward pool is active - bool active = 6 [ - (gogoproto.moretags) = "yaml:\"active\"" - ]; -} - // RewardPool is the global fee pool for distribution. message RewardPool { repeated cosmos.base.v1beta1.DecCoin community_pool = 1 [ @@ -45,4 +15,17 @@ message RewardPool { (amino.dont_omitempty) = true, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins" ]; -} \ No newline at end of file + + // Timestamp of the last emission release + google.protobuf.Timestamp last_release_time = 2 [ + (gogoproto.stdtime) = true, + (gogoproto.nullable) = false, + (gogoproto.moretags) = "yaml:\"last_release_time\"" + ]; + + // Cumulative amount released from the pool (observability only) + cosmos.base.v1beta1.Coin total_released = 3 [ + (gogoproto.nullable) = false, + (gogoproto.moretags) = "yaml:\"total_released\"" + ]; +} diff --git a/tests/e2e/e2e_rest_regression_test.go b/tests/e2e/e2e_rest_regression_test.go index 30c09cb6..28bb07f7 100644 --- a/tests/e2e/e2e_rest_regression_test.go +++ b/tests/e2e/e2e_rest_regression_test.go @@ -62,9 +62,8 @@ const ( tokenFactoryParams = "/kiichain/tokenfactory/v1beta1/params" // Rewards endpoints - rewardsParams = "/kiichain/rewards/v1beta1/params" - rewardsPool = "/kiichain/rewards/v1beta1/reward-pool" - rewardsSchedule = "/kiichain/rewards/v1beta1/release-schedule" + rewardsParams = "/kiichain/rewards/v1beta1/params" + rewardsPool = "/kiichain/rewards/v1beta1/reward-pool" // Oracle Endpoints oracleExchangeRates = "/kiichain/oracle/v1beta1/denoms/exchange_rates" @@ -106,7 +105,6 @@ func (s *IntegrationTestSuite) testRestInterfaces() { {tokenFactoryParams, 200}, {rewardsParams, 200}, {rewardsPool, 200}, - {rewardsSchedule, 200}, // EVM endpoints {evmBaseFee, 200}, {evmParams, 200}, diff --git a/tests/e2e/e2e_rewards_test.go b/tests/e2e/e2e_rewards_test.go index 2bfe01b3..07a82b7d 100644 --- a/tests/e2e/e2e_rewards_test.go +++ b/tests/e2e/e2e_rewards_test.go @@ -16,30 +16,26 @@ import ( rewardstypes "github.com/kiichain/kiichain/v7/x/rewards/types" ) -// testRewardUpdate Tests a change to the reward schedule +// testRewardUpdate tests inflation-based emissions after funding the pool and setting supply_base func (s *IntegrationTestSuite) testRewardUpdate() { - // Prep info var ( valIdx = 0 c = s.chainA denom = "akii" ) - // Amount 1k kii bigAkii, ok := math.NewIntFromString("1000000000000000000000") s.Require().True(ok) amount := sdk.NewCoin(denom, bigAkii) - // Time - now := time.Now() - endTime := now.Add(time.Minute) + supplyBase, ok := math.NewIntFromString("1000000000000000000000000") // 1e24 + s.Require().True(ok) + chainEndpoint := fmt.Sprintf("http://%s", s.valResources[s.chainA.id][0].GetHostPort("1317/tcp")) senderAddress, err := s.chainA.validators[0].keyInfo.GetAddress() s.Require().NoError(err) - // Get initial balance initialBalance, err := getSpecificBalance(chainEndpoint, senderAddress.String(), denom) s.Require().NoError(err) - // Get validator addresses validatorA, err := s.chainA.validators[0].keyInfo.GetAddress() s.Require().NoError(err) validatorB, err := s.chainA.validators[1].keyInfo.GetAddress() @@ -47,96 +43,61 @@ func (s *IntegrationTestSuite) testRewardUpdate() { valOperAddressA := sdk.ValAddress(validatorA.Bytes()).String() valOperAddressB := sdk.ValAddress(validatorB.Bytes()).String() - // Get their rewards initialRewardsA, err := queryRewardFrom(chainEndpoint, validatorA.String(), valOperAddressA) s.Require().NoError(err) initialRewardsB, err := queryRewardFrom(chainEndpoint, validatorB.String(), valOperAddressB) s.Require().NoError(err) initialRewards := initialRewardsA.Rewards.Add(initialRewardsB.Rewards...) - // 1. Fund pool via CLI s.fundRewardPool(c, valIdx, amount, senderAddress.String()) - // Check balance loss balance, err := getSpecificBalance(chainEndpoint, senderAddress.String(), denom) s.Require().NoError(err) s.Require().True(initialBalance.Sub(amount).IsGTE(balance)) - // Check pool change rewardResponse, err := queryRewardPool(chainEndpoint) s.Require().NoError(err) pool := rewardResponse.RewardPool.CommunityPool s.Require().False(pool.AmountOf(denom).IsZero()) + initialPoolAmount := pool.AmountOf(denom) + initialTotalReleased := rewardResponse.RewardPool.TotalReleased - // 2. Create and pass proposal to change schedule - s.passScheduleProposal(chainEndpoint, amount, senderAddress.String(), endTime) + s.passRewardsParamsProposal(chainEndpoint, senderAddress.String(), supplyBase) - // Query changes - scheduleResponse, err := queryReleaseSchedule(chainEndpoint) - s.Require().NoError(err) - schedule := scheduleResponse.ReleaseSchedule - s.Require().Equal(schedule.TotalAmount, amount) - s.Require().True(schedule.Active) - - // Wait time for blocks time.Sleep(time.Second * 10) - // Check schedule change - scheduleResponse, err = queryReleaseSchedule(chainEndpoint) + rewardResponse, err = queryRewardPool(chainEndpoint) s.Require().NoError(err) - finalSchedule := scheduleResponse.ReleaseSchedule - s.T().Logf("Scheduled amt before %s vs after %s", schedule.ReleasedAmount.Amount.String(), finalSchedule.ReleasedAmount.Amount.String()) - s.Require().True(schedule.ReleasedAmount.Amount.LT(finalSchedule.ReleasedAmount.Amount)) - s.Require().True(schedule.Active) + finalPool := rewardResponse.RewardPool + s.T().Logf("Pool before %s vs after %s", initialPoolAmount.String(), finalPool.CommunityPool.AmountOf(denom).String()) + s.Require().True(finalPool.CommunityPool.AmountOf(denom).LT(initialPoolAmount)) + if !initialTotalReleased.IsNil() && !initialTotalReleased.IsZero() { + s.Require().True(finalPool.TotalReleased.Amount.GT(initialTotalReleased.Amount)) + } else { + s.Require().False(finalPool.TotalReleased.IsZero()) + } - // 3. Check that the sum of validator rewards is at least initial + the released amt finalRewardsA, err := queryRewardFrom(chainEndpoint, validatorA.String(), valOperAddressA) s.Require().NoError(err) finalRewardsB, err := queryRewardFrom(chainEndpoint, validatorB.String(), valOperAddressB) s.Require().NoError(err) - // Total + initialAkii := initialRewards.AmountOf(denom) - initialPlusScheduled := initialAkii.Add(schedule.ReleasedAmount.Amount.ToLegacyDec()) finalAkii := finalRewardsB.Rewards.AmountOf(denom).Add(finalRewardsA.Rewards.AmountOf(denom)) - s.T().Logf("Reward amt before %s vs after %s", initialAkii.String(), initialPlusScheduled.String()) - s.Require().True(finalAkii.GT(initialPlusScheduled)) -} - -// queryReleaseSchedule returns schedule information from the chain -func queryReleaseSchedule(endpoint string) (rewardstypes.QueryReleaseScheduleResponse, error) { - var res rewardstypes.QueryReleaseScheduleResponse - - // Construct the full URL - url := fmt.Sprintf("%s/kiichain/rewards/v1beta1/release-schedule", endpoint) - - // Make HTTP GET request - body, err := httpGet(url) - if err != nil { - return res, err - } - - // Unmarshal JSON response - if err := cdc.UnmarshalJSON(body, &res); err != nil { - return res, err - } - - return res, nil + s.T().Logf("Reward amt before %s vs after %s", initialAkii.String(), finalAkii.String()) + s.Require().True(finalAkii.GT(initialAkii)) } -// queryRewardPool returns reward pool information from the chain func queryRewardPool(endpoint string) (rewardstypes.QueryRewardPoolResponse, error) { var res rewardstypes.QueryRewardPoolResponse - // Construct the full URL url := fmt.Sprintf("%s/kiichain/rewards/v1beta1/reward-pool", endpoint) - // Make HTTP GET request body, err := httpGet(url) if err != nil { return res, err } - // Unmarshal JSON response if err := cdc.UnmarshalJSON(body, &res); err != nil { return res, err } @@ -144,20 +105,16 @@ func queryRewardPool(endpoint string) (rewardstypes.QueryRewardPoolResponse, err return res, nil } -// queryRewardFrom returns reward from validator from the chain func queryRewardFrom(endpoint string, address string, valoperAddress string) (types.QueryDelegationRewardsResponse, error) { var res types.QueryDelegationRewardsResponse - // Construct the full URL url := fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/rewards/%s", endpoint, address, valoperAddress) - // Make HTTP GET request body, err := httpGet(url) if err != nil { return res, err } - // Unmarshal JSON response if err := cdc.UnmarshalJSON(body, &res); err != nil { return res, err } @@ -165,7 +122,6 @@ func queryRewardFrom(endpoint string, address string, valoperAddress string) (ty return res, nil } -// fundRewardPool adds funds to the rewards pool func (s *IntegrationTestSuite) fundRewardPool(c *chain, valIdx int, amount sdk.Coin, sender string) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() @@ -188,51 +144,41 @@ func (s *IntegrationTestSuite) fundRewardPool(c *chain, valIdx int, amount sdk.C s.executeKiichainTxCommand(ctx, c, kiichainCommand, valIdx, s.defaultExecValidation(c, valIdx)) } -// passScheduleProposal passes a storage proposal -func (s *IntegrationTestSuite) passScheduleProposal(chainEndpoint string, amount sdk.Coin, sender string, endTime time.Time) { - // Write proposal - s.writeScheduleProposal(s.chainA, amount, endTime) +func (s *IntegrationTestSuite) passRewardsParamsProposal(chainEndpoint string, sender string, supplyBase math.Int) { + s.writeRewardsParamsProposal(s.chainA, supplyBase) - // Create command proposalCounter++ - submitGovFlags := []string{configFile(proposalAddSchedule)} + submitGovFlags := []string{configFile(proposalUpdateRewardsParams)} depositGovFlags := []string{strconv.Itoa(proposalCounter), depositAmount.String()} voteGovFlags := []string{strconv.Itoa(proposalCounter), "yes"} - // Create and pass proposal - s.submitGovProposal(chainEndpoint, sender, proposalCounter, "ChangeSchedule", submitGovFlags, depositGovFlags, voteGovFlags, "vote") + s.submitGovProposal(chainEndpoint, sender, proposalCounter, "UpdateParams", submitGovFlags, depositGovFlags, voteGovFlags, "vote") } -// writeScheduleProposal stores a file with the change schedule proposal -func (s *IntegrationTestSuite) writeScheduleProposal(c *chain, amount sdk.Coin, endTime time.Time) { +func (s *IntegrationTestSuite) writeRewardsParamsProposal(c *chain, supplyBase math.Int) { body := `{ "messages": [ { - "@type": "/kiichain.rewards.v1beta1.MsgChangeSchedule", + "@type": "/kiichain.rewards.v1beta1.MsgUpdateParams", "authority": "kii10d07y265gmmuvt4z0w9aw880jnsr700jrff0qv", - "schedule": { - "total_amount": { - "denom": "%s", - "amount": "%s" - }, - "released_amount": { - "denom": "%s", - "amount": "0" - }, - "end_time": "%s", - "last_release_time": "0001-01-01T00:00:00Z", - "active": true + "params": { + "token_denom": "akii", + "goal_bonded": "0.670000000000000000", + "inflation_min": "0.000000000000000000", + "inflation_max": "0.200000000000000000", + "supply_base": "%s", + "inflation_rate_change": "0.130000000000000000" } } ], "metadata": "ipfs://CID", "deposit": "1000akii", - "title": "Add Schedule", - "summary": "initial schedule" + "title": "Enable Rewards Emissions", + "summary": "set supply_base to enable inflation-based emissions" }` - propMsgBody := fmt.Sprintf(body, amount.Denom, amount.Amount.String(), amount.Denom, endTime.UTC().Format(time.RFC3339Nano)) + propMsgBody := fmt.Sprintf(body, supplyBase.String()) - err := writeFile(filepath.Join(c.validators[0].configDir(), "config", proposalAddSchedule), []byte(propMsgBody)) + err := writeFile(filepath.Join(c.validators[0].configDir(), "config", proposalUpdateRewardsParams), []byte(propMsgBody)) s.Require().NoError(err) } diff --git a/tests/e2e/e2e_setup_test.go b/tests/e2e/e2e_setup_test.go index 4e4cef09..3b7cf08d 100644 --- a/tests/e2e/e2e_setup_test.go +++ b/tests/e2e/e2e_setup_test.go @@ -81,7 +81,7 @@ const ( proposalSoftwareUpgrade = "proposal_software_upgrade.json" proposalCancelSoftwareUpgrade = "proposal_cancel_software_upgrade.json" proposalRegisterERC20 = "proposal_register_erc20.json" - proposalAddSchedule = "proposal_add_schedule.json" + proposalUpdateRewardsParams = "proposal_update_rewards_params.json" hermesBinary = "hermes" hermesConfigWithGasPrices = "/root/.hermes/config.toml" diff --git a/x/feeabstraction/module.go b/x/feeabstraction/module.go index 3e91963c..46fbe55a 100644 --- a/x/feeabstraction/module.go +++ b/x/feeabstraction/module.go @@ -27,8 +27,8 @@ import ( var ( _ module.AppModuleBasic = AppModuleBasic{} _ module.HasGenesisBasics = AppModuleBasic{} + _ appmodule.AppModule = AppModule{} _ appmodule.HasBeginBlocker = AppModule{} - _ module.AppModule = AppModule{} //nolint:staticcheck _ module.HasABCIGenesis = AppModule{} ) diff --git a/x/oracle/module.go b/x/oracle/module.go index 231e3373..bccbfe7e 100644 --- a/x/oracle/module.go +++ b/x/oracle/module.go @@ -11,6 +11,8 @@ import ( abci "github.com/cometbft/cometbft/abci/types" + "cosmossdk.io/core/appmodule" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" @@ -23,8 +25,8 @@ import ( ) var ( - _ module.AppModule = AppModule{} //nolint:staticcheck _ module.AppModuleBasic = AppModuleBasic{} // Indirect implement the AppModuleBasic interface + _ appmodule.AppModule = AppModule{} ) // ConsensusVersion defines the current x/oracle module consensus version. diff --git a/x/rewards/README.md b/x/rewards/README.md index e606058c..eaa6774e 100644 --- a/x/rewards/README.md +++ b/x/rewards/README.md @@ -1,149 +1,69 @@ # Rewards -The rewards module adds the distribution of rewards from a specific community pool. -Anyone can fund the pool but to change or initiate a reward distribution, a proposal -needs to be passed. +The rewards module distributes prefunded tokens from a community pool into the +fee collector each block, using an inflation curve driven by the bonded ratio. +Anyone can fund the pool; governance enables emissions by setting `supply_base`. -## Flow: -1. Fund community pool with reward -2. Create and pass a proposal to create a release schedule -3. At the end of every block, a linear % of the reward will be forward to distribution -4. When the end time of the release is reached, all rewards will have been given away and it will go inactive +## Flow -## Internal state: -To properly release on time, calculate rewards and keep track, we have a RewardReleaser with the following internal information: +1. Fund the community pool (`MsgFundPool`) +2. Pass a gov proposal to set params, including a non-zero `supply_base` (`MsgUpdateParams`) +3. Each begin-block, release `inflation(bondedRatio) × supply_base × Δt / year` into `fee_collector` +4. Emissions continue until the pool is empty or governance sets `supply_base` back to `0` -```go -type RewardReleaser struct { - // Total amount to be rewarded - TotalAmount types.Coin `protobuf:"bytes,1,opt,name=total_amount,json=totalAmount,proto3" json:"total_amount" yaml:"total_amount"` - // Amount released - ReleasedAmount types.Coin `protobuf:"bytes,2,opt,name=released_amount,json=releasedAmount,proto3" json:"released_amount" yaml:"released_amount"` - // Timestamp of end of release - EndTime time.Time `protobuf:"bytes,3,opt,name=end_time,json=endTime,proto3,stdtime" json:"end_time" yaml:"end_time"` - // Last height released - LastReleaseTime time.Time `protobuf:"bytes,5,opt,name=last_release_time,json=lastReleaseTime,proto3,stdtime" json:"last_release_time" yaml:"last_release_time"` - // If reward pool is active - Active bool `protobuf:"varint,6,opt,name=active,proto3" json:"active,omitempty" yaml:"active"` -} -``` +## Emission formula -At the end of each block, if the releaser is active: -- It will calculate the amt to be distributed, linearly across time based on the last release and the current block time. -- If the amt to be distributed is zero, it goes inactive -- It sends the amt from the pool to the fee collector -- It increases the released amt, the last release time and the community pool with the changes. - -## Edge cases and decisions - -A few decisions were taken when building the module, which must be taken into account when creating governance proposals and interacting with the module: -- **Overriding schedules**: A new schedule will override the previous one and will not distribute any remaining funds from the previous schedule. This can be used to update amounts or end times, but if a schedule is changed midway it may result in leftover funds in the pool. -- **Changing denoms**: The token denom being distributed can be changed, but it was not meant to support multiple distributions at the same time. It was merely set as a future possibility for other tokens to be rewarded instead of the native token. If the denom needs to be changed, the correct approach is to wait for the current schedule to end so no tokens are left over, then change the base denom and create a new schedule. Otherwise, some tokens may be left over in the pool. - - If a denom is changed, the `QueryParamsRequest` will return this new denom, even if there is still a current schedule on the old denom. -- **Leftover tokens**: If tokens are left over by any schedule due to overrides, they can be distributed in the future with a new schedule, but not recovered. With this in mind, the recommended approach when changing a schedule is to first halt it, then include its leftover funds in the next schedule, assuming it's on the same token. +``` +inflation = clamp((1 - bondedRatio/goalBonded) × inflationRateChange × bondedRatio, inflationMin, inflationMax) +amount = inflation × supplyBase × elapsedNs / nsPerYear +pay = min(amount, poolBalance) +``` -## Messages +Where: -### FundPool +- `bondedRatio` — fraction of the token supply currently staked (read from x/staking) +- `goalBonded` — target stake ratio; the curve peaks at `goalBonded/2` and hits zero at `goalBonded` +- `inflationRateChange` — curve steepness (default `0.13`) +- `inflationMin` / `inflationMax` — floor and ceiling on the emission rate +- `supplyBase` — notional base that sizes annual provisions (`annual = inflation × supplyBase`). + It is **not** the chain total supply; it is a governance knob for emission scale. + Defaults to `0` (emissions off). -Sends funds to the community pool, to be used in a future release. +## Internal state ```go -message MsgFundPool { - string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; - cosmos.base.v1beta1.Coin amount = 2 [ - (gogoproto.moretags) = "yaml:\"amount\"", - (amino.encoding) = "legacy_coin" - ]; +type RewardPool struct { + CommunityPool sdk.DecCoins + LastReleaseTime time.Time + TotalReleased sdk.Coin // cumulative observability counter } ``` -**State Modifications:** - -- The community pool funds will increase, as well as the module's balance - -### ChangeSchedule -Set the schedule to match what is sent. Only the governor can utilize this call, others need to pass a proposal. +Params: ```go -message MsgChangeSchedule { - // authority is the address of the governance account. - string authority = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; - - - // New information for the schedule - ReleaseSchedule schedule = 2 [ (gogoproto.nullable) = false ]; -} - -message ReleaseSchedule { - // Total amount to be rewarded - cosmos.base.v1beta1.Coin total_amount = 1 [ - (gogoproto.moretags) = "yaml:\"total_amount\"" - ]; - // Amount released - cosmos.base.v1beta1.Coin released_amount = 2 [ - (gogoproto.moretags) = "yaml:\"released_amount\"" - ]; - // Timestamp of end of release - google.protobuf.Timestamp end_time = 3 [ - (gogoproto.stdtime) = true, - (gogoproto.moretags) = "yaml:\"end_time\"" - ]; - // Last height released - google.protobuf.Timestamp last_release_time = 5 [ - (gogoproto.stdtime) = true, - (gogoproto.moretags) = "yaml:\"last_release_time\"" - ]; - // If reward pool is active - bool active = 6 [ - (gogoproto.moretags) = "yaml:\"active\"" - ]; +type Params struct { + TokenDenom string + GoalBonded math.LegacyDec // default 0.67 + InflationMin math.LegacyDec // default 0 + InflationMax math.LegacyDec // default 0.20 + SupplyBase math.Int // default 0 + InflationRateChange math.LegacyDec // default 0.13 } ``` -**State Modifications:** - -- Safety check the following - - Denom of the amt must be the one being used - - End time must be in the future - - Funds must be available in the pool -- Changes the reward release schedule to match what is sent - -### Update Params - -Changes module params. Only the governor can utilize this call, others need to pass a proposal. +## Messages -```go -message MsgUpdateParams { - // authority is the address of the governance account. - string authority = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; - - // params defines the x/rewards parameters to update. - // - // NOTE: All parameters must be supplied. - Params params = 2 [ (gogoproto.nullable) = false ]; -} -message Params { - // Minimal deposit - string governance_min_deposit = 1; +### FundPool - // Denom used - string token_denom = 2; -} -``` +Sends funds to the community pool. -**State Modifications:** -- Changes the token_denom and min deposit +### UpdateParams -## Other important flows -The releaser has a few edge cases that happen when it is initializing or going inactive: +Governance-only. Sets all module params. Setting `supply_base > 0` enables emissions; +setting it to `0` disables them. -### First iteration: -- There is no previous release time, so we cannot calculate a reward distribution -- Instead of calculating the reward, we just set the last release as the block time -- Next iteration will cover it well +## First iteration -### Last iteration: -- As the first release is delayed, so will be the last one -- Once the EndTime is passed, all the remaining reward will be distributed -- The releaser will just go inactive a block after, when there is no amt to distribute +When `last_release_time` is zero, BeginBlocker stamps the current block time and +skips release. The next block starts accruing. diff --git a/x/rewards/client/cli/query.go b/x/rewards/client/cli/query.go index 3707e169..21cff33c 100644 --- a/x/rewards/client/cli/query.go +++ b/x/rewards/client/cli/query.go @@ -24,7 +24,6 @@ func GetQueryCmd() *cobra.Command { cmd.AddCommand( GetCmdQueryParams(), - GetCmdQueryReleaseSchedule(), GetCmdQueryRewardPool(), ) @@ -58,33 +57,6 @@ func GetCmdQueryParams() *cobra.Command { return cmd } -// GetCmdQueryReleaseSchedule implements the release-schedule query command. -func GetCmdQueryReleaseSchedule() *cobra.Command { - cmd := &cobra.Command{ - Use: "release-schedule", - Short: "Query the current rewards release schedule", - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientQueryContext(cmd) - if err != nil { - return err - } - - queryClient := types.NewQueryClient(clientCtx) - - res, err := queryClient.ReleaseSchedule(context.Background(), &types.QueryReleaseScheduleRequest{}) - if err != nil { - return err - } - - return clientCtx.PrintProto(res) - }, - } - - flags.AddQueryFlagsToCmd(cmd) - return cmd -} - // GetCmdQueryRewardPool implements the reward-pool query command. func GetCmdQueryRewardPool() *cobra.Command { cmd := &cobra.Command{ diff --git a/x/rewards/client/cli/tx.go b/x/rewards/client/cli/tx.go index 35d413ce..98131191 100644 --- a/x/rewards/client/cli/tx.go +++ b/x/rewards/client/cli/tx.go @@ -26,7 +26,6 @@ func GetTxCmd() *cobra.Command { cmd.AddCommand( NewFundPoolCmd(), NewUpdateParamsCmd(), - NewChangeScheduleCmd(), ) return cmd @@ -88,33 +87,3 @@ $ %s tx gov submit-proposal update-rewards-params --from m flags.AddTxFlagsToCmd(cmd) return cmd } - -// NewChangeScheduleCmd implements the change-schedule tx command. -func NewChangeScheduleCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "change-schedule [schedule-json]", - Short: "Change schedule information (gov proposal)", - Long: `Change schedule information through a governance proposal. Example: -$ %s tx gov submit-proposal change-schedule --from mykey -`, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - - var schedule types.ReleaseSchedule - if err := clientCtx.Codec.UnmarshalJSON([]byte(args[0]), &schedule); err != nil { - return fmt.Errorf("failed to parse params: %w", err) - } - - msg := types.NewMsgChangeSchedule(clientCtx.GetFromAddress().String(), schedule) - - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) - }, - } - - flags.AddTxFlagsToCmd(cmd) - return cmd -} diff --git a/x/rewards/keeper/abci.go b/x/rewards/keeper/abci.go index c542d33b..4ecba7b9 100644 --- a/x/rewards/keeper/abci.go +++ b/x/rewards/keeper/abci.go @@ -1,8 +1,6 @@ package keeper import ( - "fmt" - "cosmossdk.io/math" "github.com/cosmos/cosmos-sdk/telemetry" @@ -11,122 +9,118 @@ import ( "github.com/kiichain/kiichain/v7/x/rewards/types" ) -// haltSchedule logs err, marks the release schedule inactive, and returns nil so -// the error never reaches FinalizeBlock and halts the chain -func (k Keeper) haltSchedule(ctx sdk.Context, schedule types.ReleaseSchedule, err error) error { - k.Logger(ctx).Error("halting release schedule due to error", "error", err) - schedule.Active = false - return k.ReleaseSchedule.Set(ctx, schedule) -} - -// BeginBlocker calculates reward amt and sends it to the distribution pool +// BeginBlocker calculates the inflation-based reward amount and sends it to the fee collector. func (k Keeper) BeginBlocker(ctx sdk.Context) error { - // Apply telemetry metrics defer telemetry.ModuleMeasureSince(types.ModuleName, telemetry.Now(), telemetry.MetricKeyBeginBlocker) - // Get release schedule - schedule, err := k.ReleaseSchedule.Get(ctx) + params, err := k.Params.Get(ctx) if err != nil { return err } - // Early exit if inactive or nothing to release - if !schedule.Active || schedule.TotalAmount.IsZero() { + // supply_base == 0 disables emissions until governance configures it + if params.SupplyBase.IsZero() { return nil } - // If active and there is no previous time stamp, set it as current block's and skip this time - if schedule.LastReleaseTime.IsZero() { - schedule.LastReleaseTime = ctx.BlockTime() - return k.ReleaseSchedule.Set(ctx, schedule) - } - - // Calculate the amount to distribute this block - amountToDistribute, err := types.CalculateReward(ctx.BlockTime(), schedule) + rewardPool, err := k.RewardPool.Get(ctx) if err != nil { - return k.haltSchedule(ctx, schedule, err) + return err } - if amountToDistribute.IsZero() { - if schedule.ReleasedAmount.IsGTE(schedule.TotalAmount) { - schedule.Active = false - return k.ReleaseSchedule.Set(ctx, schedule) + poolBalance := rewardPool.CommunityPool.AmountOf(params.TokenDenom).TruncateInt() + if !poolBalance.IsPositive() { + // Advance the clock while empty so a later fund does not dump accrued Δt + if !rewardPool.LastReleaseTime.IsZero() && ctx.BlockTime().After(rewardPool.LastReleaseTime) { + rewardPool.LastReleaseTime = ctx.BlockTime() + return k.RewardPool.Set(ctx, rewardPool) } return nil } - // Get the current RewardPool from state - rewardPool, err := k.RewardPool.Get(ctx) + // First active block: stamp time and skip release + if rewardPool.LastReleaseTime.IsZero() { + rewardPool.LastReleaseTime = ctx.BlockTime() + return k.RewardPool.Set(ctx, rewardPool) + } + + bondedRatio, err := k.stakingKeeper.BondedRatio(ctx) if err != nil { - return err + k.Logger(ctx).Error("failed to read bonded ratio", "error", err) + // Advance the clock so a transient staking read failure does not accrue Δt + return k.advanceLastReleaseTime(ctx, rewardPool) } - // Set up coins - coinsToDistribute := sdk.NewCoins(amountToDistribute) + amountToDistribute, inflation := types.CalculateReward( + ctx.BlockTime(), + rewardPool.LastReleaseTime, + bondedRatio, + params, + ) - // Verify the CommunityPool has sufficient balance for the denom before transferring - poolAmount := rewardPool.CommunityPool.AmountOf(amountToDistribute.Denom) - if math.LegacyNewDecFromInt(amountToDistribute.Amount).GT(poolAmount) { - return k.haltSchedule(ctx, schedule, fmt.Errorf("community pool has insufficient balance for %s: pool has %s, need %s", - amountToDistribute.Denom, poolAmount, amountToDistribute.Amount)) + // Cap at remaining pool balance so emissions run until the pool is dry + amountToDistribute.Amount = math.MinInt(amountToDistribute.Amount, poolBalance) + + if amountToDistribute.IsZero() { + return nil } - // Send to distribution pool + coinsToDistribute := sdk.NewCoins(amountToDistribute) + if err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, k.feeCollectorName, coinsToDistribute); err != nil { - return k.haltSchedule(ctx, schedule, fmt.Errorf("failed to send rewards to fee collector: %w", err)) + k.Logger(ctx).Error("failed to send rewards to fee collector", "error", err) + // Skip this interval rather than dumping a catch-up emission once bank recovers + return k.advanceLastReleaseTime(ctx, rewardPool) } - // Deduct from RewardPool using SafeSub so a divergence cannot panic the chain. remaining, hasNeg := rewardPool.CommunityPool.SafeSub(sdk.NewDecCoinsFromCoins(coinsToDistribute...)) if hasNeg { - return k.haltSchedule(ctx, schedule, fmt.Errorf("community pool subtraction resulted in negative balance for denom %s", amountToDistribute.Denom)) + k.Logger(ctx).Error("community pool subtraction resulted in negative balance", + "denom", amountToDistribute.Denom) + // Funds may already have left the module account; still advance the clock + return k.advanceLastReleaseTime(ctx, rewardPool) } rewardPool.CommunityPool = remaining + rewardPool.LastReleaseTime = ctx.BlockTime() - // Save change - if err := k.RewardPool.Set(ctx, rewardPool); err != nil { - return err + if rewardPool.TotalReleased.IsNil() || rewardPool.TotalReleased.IsZero() { + rewardPool.TotalReleased = amountToDistribute + } else { + rewardPool.TotalReleased = rewardPool.TotalReleased.Add(amountToDistribute) } - // Update release schedule - schedule.LastReleaseTime = ctx.BlockTime() - schedule.ReleasedAmount = schedule.ReleasedAmount.Add(amountToDistribute) - if err := k.ReleaseSchedule.Set(ctx, schedule); err != nil { + if err := k.RewardPool.Set(ctx, rewardPool); err != nil { return err } ctx.EventManager().EmitEvent(sdk.NewEvent( types.EventTypeRewardDistributed, sdk.NewAttribute(types.AttributeKeyAmount, amountToDistribute.String()), - sdk.NewAttribute(types.AttributeKeyTotalReleased, schedule.ReleasedAmount.String()), + sdk.NewAttribute(types.AttributeKeyTotalReleased, rewardPool.TotalReleased.String()), + sdk.NewAttribute(types.AttributeKeyInflationRate, inflation.String()), + sdk.NewAttribute(types.AttributeKeyBondedRatio, bondedRatio.String()), )) - k.WriteRewardMetrics(ctx, amountToDistribute, schedule.ReleasedAmount) + k.WriteRewardMetrics(ctx, amountToDistribute, rewardPool.TotalReleased) return nil } -// WriteRewardMetrics writes reward information to telemetry metrics -func (k Keeper) WriteRewardMetrics(ctx sdk.Context, distributed, total sdk.Coin) { - // Forgo telemetry on conversion errors - distFloat, err := distributed.Amount.ToLegacyDec().Float64() - if err != nil { - k.Logger(ctx).Error("failed to convert distributed amount to float64", - "error", err, - "distributed", distributed.String(), - "denom", distributed.Denom, - ) - return - } - totalFloat, err := total.Amount.ToLegacyDec().Float64() - if err != nil { - k.Logger(ctx).Error("failed to convert total release amount to float64", - "error", err, - "total", total.String(), - "denom", total.Denom, - ) - return +// advanceLastReleaseTime persists the current block time so a skipped release +// interval does not accumulate into a later catch-up emission. +func (k Keeper) advanceLastReleaseTime(ctx sdk.Context, rewardPool types.RewardPool) error { + if rewardPool.LastReleaseTime.IsZero() || !ctx.BlockTime().After(rewardPool.LastReleaseTime) { + return nil } + rewardPool.LastReleaseTime = ctx.BlockTime() + return k.RewardPool.Set(ctx, rewardPool) +} + +// WriteRewardMetrics writes reward information to telemetry metrics. +// Conversion failures yield zero gauges; telemetry is best-effort. +func (k Keeper) WriteRewardMetrics(_ sdk.Context, distributed, total sdk.Coin) { + distFloat, _ := distributed.Amount.ToLegacyDec().Float64() + totalFloat, _ := total.Amount.ToLegacyDec().Float64() telemetry.ModuleSetGauge( types.ModuleName, diff --git a/x/rewards/keeper/abci_error_test.go b/x/rewards/keeper/abci_error_test.go new file mode 100644 index 00000000..9e2937a7 --- /dev/null +++ b/x/rewards/keeper/abci_error_test.go @@ -0,0 +1,156 @@ +package keeper_test + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/stretchr/testify/require" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + + dbm "github.com/cosmos/cosmos-db" + + "cosmossdk.io/log" + "cosmossdk.io/math" + "cosmossdk.io/store" + "cosmossdk.io/store/metrics" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + + "github.com/kiichain/kiichain/v7/x/rewards/keeper" + "github.com/kiichain/kiichain/v7/x/rewards/types" +) + +type mockBankKeeper struct { + sendErr error +} + +func (m mockBankKeeper) SendCoinsFromModuleToModule(context.Context, string, string, sdk.Coins) error { + return m.sendErr +} + +func (m mockBankKeeper) SendCoinsFromAccountToModule(context.Context, sdk.AccAddress, string, sdk.Coins) error { + return nil +} + +func (m mockBankKeeper) GetBalance(context.Context, sdk.AccAddress, string) sdk.Coin { + return sdk.Coin{} +} + +type mockStakingKeeper struct { + ratio math.LegacyDec + err error +} + +func (m mockStakingKeeper) BondedRatio(context.Context) (math.LegacyDec, error) { + return m.ratio, m.err +} + +func setupRewardsKeeper( + t *testing.T, + bank types.BankKeeper, + staking types.StakingKeeper, +) (sdk.Context, keeper.Keeper) { + t.Helper() + + storeKey := storetypes.NewKVStoreKey(types.StoreKey) + db := dbm.NewMemDB() + stateStore := store.NewCommitMultiStore(db, log.NewNopLogger(), metrics.NewNoOpMetrics()) + stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) + require.NoError(t, stateStore.LoadLatestVersion()) + + registry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(registry) + + k := keeper.NewKeeper( + cdc, + runtime.NewKVStoreService(storeKey), + bank, + staking, + authtypes.NewModuleAddress("gov").String(), + authtypes.FeeCollectorName, + ) + + ctx := sdk.NewContext(stateStore, cmtproto.Header{Time: time.Now().UTC()}, false, log.NewNopLogger()) + return ctx, k +} + +func TestBeginBlockerErrorPaths(t *testing.T) { + t.Run("params missing returns error", func(t *testing.T) { + ctx, k := setupRewardsKeeper(t, mockBankKeeper{}, mockStakingKeeper{}) + err := k.BeginBlocker(ctx) + require.Error(t, err) + }) + + t.Run("reward pool missing returns error", func(t *testing.T) { + ctx, k := setupRewardsKeeper(t, mockBankKeeper{}, mockStakingKeeper{}) + params := types.DefaultParams() + params.SupplyBase = math.NewInt(1_000_000) + require.NoError(t, k.Params.Set(ctx, params)) + + err := k.BeginBlocker(ctx) + require.Error(t, err) + }) + + t.Run("bonded ratio error skips without failing block", func(t *testing.T) { + ctx, k := setupRewardsKeeper(t, + mockBankKeeper{}, + mockStakingKeeper{err: errors.New("staking unavailable")}, + ) + + params := types.DefaultParams() + params.SupplyBase = math.NewInt(1_000_000) + require.NoError(t, k.Params.Set(ctx, params)) + + now := ctx.BlockTime() + require.NoError(t, k.RewardPool.Set(ctx, types.RewardPool{ + CommunityPool: sdk.NewDecCoins(sdk.NewDecCoin(params.TokenDenom, math.NewInt(1_000_000))), + LastReleaseTime: now.Add(-time.Hour), + })) + + require.NoError(t, k.BeginBlocker(ctx)) + + pool, err := k.RewardPool.Get(ctx) + require.NoError(t, err) + require.True(t, pool.TotalReleased.IsZero() || pool.TotalReleased.IsNil() || pool.TotalReleased.Amount.IsZero()) + // Clock advances so a later successful block does not dump accrued Δt + require.True(t, pool.LastReleaseTime.Equal(now)) + }) + + t.Run("bank send failure skips without failing block", func(t *testing.T) { + ctx, k := setupRewardsKeeper(t, + mockBankKeeper{sendErr: errors.New("insufficient funds")}, + mockStakingKeeper{ratio: math.LegacyNewDecWithPrec(30, 2)}, + ) + + params := types.DefaultParams() + params.SupplyBase = math.NewInt(1_000_000_000_000) + require.NoError(t, k.Params.Set(ctx, params)) + + now := ctx.BlockTime() + require.NoError(t, k.RewardPool.Set(ctx, types.RewardPool{ + CommunityPool: sdk.NewDecCoins(sdk.NewDecCoin(params.TokenDenom, math.NewInt(1_000_000))), + LastReleaseTime: now.Add(-time.Hour), + })) + + require.NoError(t, k.BeginBlocker(ctx)) + + pool, err := k.RewardPool.Get(ctx) + require.NoError(t, err) + require.True(t, pool.LastReleaseTime.Equal(now)) + require.True(t, pool.TotalReleased.IsZero() || pool.TotalReleased.IsNil() || pool.TotalReleased.Amount.IsZero()) + }) +} + +func TestWriteRewardMetrics(t *testing.T) { + ctx, k := setupRewardsKeeper(t, mockBankKeeper{}, mockStakingKeeper{}) + // Happy path: normal amounts convert cleanly + k.WriteRewardMetrics(ctx, sdk.NewCoin("akii", math.NewInt(100)), sdk.NewCoin("akii", math.NewInt(200))) +} diff --git a/x/rewards/keeper/abci_test.go b/x/rewards/keeper/abci_test.go index 0ca26c38..2a8c29be 100644 --- a/x/rewards/keeper/abci_test.go +++ b/x/rewards/keeper/abci_test.go @@ -10,291 +10,261 @@ import ( "github.com/kiichain/kiichain/v7/x/rewards/types" ) -func (suite *KeeperTestSuite) TestEndBlocker() { - // Set up default params +func (suite *KeeperTestSuite) TestBeginBlocker() { defaultParams := types.DefaultParams() + defaultParams.SupplyBase = math.NewInt(1_000_000_000_000) // 1e12 err := suite.App.RewardsKeeper.Params.Set(suite.Ctx, defaultParams) suite.Require().NoError(err) - // Fund the reward pool first - err = suite.App.RewardsKeeper.FundCommunityPool( - suite.Ctx, - sdk.NewCoin(defaultParams.TokenDenom, math.NewInt(100000)), - suite.TestAccs[0]) - suite.Require().NoError(err) - - now := time.Now() + now := time.Now().UTC() denom := defaultParams.TokenDenom testCases := []struct { - name string - initialSchedule types.ReleaseSchedule - initialPool sdk.DecCoins + name string + params types.Params + initialPool types.RewardPool + // moduleBankBalance is the actual coins placed in the rewards module + // account before BeginBlocker. Defaults to TruncateInt(CommunityPool) + // when nil; set explicitly for bank-failure cases. + moduleBankBalance *math.Int blockTime time.Time - expectedChange bool - expectedSchedule types.ReleaseSchedule - expectedChangeAmount sdk.Coin + expectTransfer bool + expectedChangeAmount func(bonded math.LegacyDec) sdk.Coin + expectLastReleaseSet bool + expectLastReleaseAdv bool }{ { - name: "inactive schedule - no action", - initialSchedule: types.ReleaseSchedule{ - Active: false, - }, - blockTime: now.Add(time.Hour), - expectedChange: false, - }, - { - name: "zero total amount - no action", - initialSchedule: types.ReleaseSchedule{ - Active: true, - TotalAmount: sdk.NewCoin(denom, math.ZeroInt()), + name: "supply base zero - no action", + params: func() types.Params { + p := defaultParams + p.SupplyBase = math.ZeroInt() + return p + }(), + initialPool: types.RewardPool{ + CommunityPool: sdk.NewDecCoins(sdk.NewDecCoin(denom, math.NewInt(100000))), + LastReleaseTime: now, }, blockTime: now.Add(time.Hour), - expectedChange: false, + expectTransfer: false, }, { - name: "first run - sets timestamp but no distribution", - initialSchedule: types.ReleaseSchedule{ - Active: true, - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000)), - ReleasedAmount: sdk.NewCoin(denom, math.ZeroInt()), - LastReleaseTime: time.Time{}, - EndTime: now.Add(time.Hour * 2), - }, - blockTime: now, - expectedSchedule: types.ReleaseSchedule{ - Active: true, - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000)), - ReleasedAmount: sdk.NewCoin(denom, math.ZeroInt()), + name: "empty pool with prior release - advances last release time", + params: defaultParams, + initialPool: types.RewardPool{ + CommunityPool: sdk.DecCoins{}, LastReleaseTime: now, - EndTime: now.Add(time.Hour * 2), }, - expectedChange: true, - expectedChangeAmount: sdk.NewCoin(denom, math.ZeroInt()), + blockTime: now.Add(time.Hour), + expectTransfer: false, + expectLastReleaseAdv: true, }, { - name: "normal distribution - partial release", - initialSchedule: types.ReleaseSchedule{ - Active: true, - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000)), - ReleasedAmount: sdk.NewCoin(denom, math.ZeroInt()), - LastReleaseTime: now, - EndTime: now.Add(time.Hour * 2), - }, - initialPool: sdk.NewDecCoins(sdk.NewDecCoin(denom, math.NewInt(1000))), - blockTime: now.Add(time.Hour), - expectedSchedule: types.ReleaseSchedule{ - Active: true, - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000)), - ReleasedAmount: sdk.NewCoin(denom, math.NewInt(500)), - LastReleaseTime: now.Add(time.Hour), - EndTime: now.Add(time.Hour * 2), + name: "empty pool with zero last release - no action", + params: defaultParams, + initialPool: types.RewardPool{ + CommunityPool: sdk.DecCoins{}, + LastReleaseTime: time.Time{}, }, - expectedChange: true, - expectedChangeAmount: sdk.NewCoin(denom, math.NewInt(500)), + blockTime: now.Add(time.Hour), + expectTransfer: false, }, { - name: "final distribution", - initialSchedule: types.ReleaseSchedule{ - Active: true, - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000)), - ReleasedAmount: sdk.NewCoin(denom, math.NewInt(900)), - LastReleaseTime: now, - EndTime: now.Add(time.Hour), - }, - initialPool: sdk.NewDecCoins(sdk.NewDecCoin(denom, math.NewInt(100))), - blockTime: now.Add(time.Hour), - expectedSchedule: types.ReleaseSchedule{ - Active: true, - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000)), - ReleasedAmount: sdk.NewCoin(denom, math.NewInt(1000)), - LastReleaseTime: now.Add(time.Hour), - EndTime: now.Add(time.Hour), + name: "first run - sets timestamp but no distribution", + params: defaultParams, + initialPool: types.RewardPool{ + CommunityPool: sdk.NewDecCoins(sdk.NewDecCoin(denom, math.NewInt(100000))), + LastReleaseTime: time.Time{}, }, - expectedChange: true, - expectedChangeAmount: sdk.NewCoin(denom, math.NewInt(100)), + blockTime: now, + expectTransfer: false, + expectLastReleaseSet: true, }, { - name: "insufficient community pool - halts schedule without transfer", - initialSchedule: types.ReleaseSchedule{ - Active: true, - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000)), - ReleasedAmount: sdk.NewCoin(denom, math.ZeroInt()), - LastReleaseTime: now, - EndTime: now.Add(time.Hour * 2), - }, - // Pool has only 100 but 500 would be released (half of 1000 over 1 of 2 hours) - initialPool: sdk.NewDecCoins(sdk.NewDecCoin(denom, math.NewInt(100))), - blockTime: now.Add(time.Hour), - expectedSchedule: types.ReleaseSchedule{ - Active: false, - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000)), - ReleasedAmount: sdk.NewCoin(denom, math.ZeroInt()), + name: "sub-unit release - zero amount skip", + params: defaultParams, + initialPool: types.RewardPool{ + CommunityPool: sdk.NewDecCoins(sdk.NewDecCoin(denom, math.NewInt(100000))), LastReleaseTime: now, - EndTime: now.Add(time.Hour * 2), }, - expectedChange: true, - expectedChangeAmount: sdk.NewCoin(denom, math.ZeroInt()), + blockTime: now.Add(time.Nanosecond), + expectTransfer: false, }, { - name: "bank transfer failure - halts schedule gracefully", - initialSchedule: types.ReleaseSchedule{ - Active: true, - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000000)), - ReleasedAmount: sdk.NewCoin(denom, math.ZeroInt()), + name: "normal distribution", + params: defaultParams, + initialPool: types.RewardPool{ + CommunityPool: sdk.NewDecCoins(sdk.NewDecCoin(denom, math.NewInt(100000))), LastReleaseTime: now, - EndTime: now.Add(time.Hour * 2), }, - // Accounting claims 1,000,000 is available but the module account only - // holds the 100,000 funded above, so the 500,000 release transfer fails - // and the schedule must be halted instead of aborting the block. - initialPool: sdk.NewDecCoins(sdk.NewDecCoin(denom, math.NewInt(1000000))), - blockTime: now.Add(time.Hour), - expectedSchedule: types.ReleaseSchedule{ - Active: false, - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000000)), - ReleasedAmount: sdk.NewCoin(denom, math.ZeroInt()), - LastReleaseTime: now, - EndTime: now.Add(time.Hour * 2), + blockTime: now.Add(time.Hour), + expectTransfer: true, + expectedChangeAmount: func(bonded math.LegacyDec) sdk.Coin { + coin, _ := types.CalculateReward(now.Add(time.Hour), now, bonded, defaultParams) + coin.Amount = math.MinInt(coin.Amount, math.NewInt(100000)) + return coin }, - expectedChange: true, - expectedChangeAmount: sdk.NewCoin(denom, math.ZeroInt()), }, { - name: "sub-unit release - skips without halting or releasing", - initialSchedule: types.ReleaseSchedule{ - Active: true, - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000000)), - ReleasedAmount: sdk.NewCoin(denom, math.ZeroInt()), + name: "accumulates total released on subsequent release", + params: defaultParams, + initialPool: types.RewardPool{ + CommunityPool: sdk.NewDecCoins(sdk.NewDecCoin(denom, math.NewInt(100000))), LastReleaseTime: now, - EndTime: now.Add(time.Hour * 24 * 365 * 10), + TotalReleased: sdk.NewCoin(denom, math.NewInt(7)), }, - initialPool: sdk.NewDecCoins(sdk.NewDecCoin(denom, math.NewInt(1000000))), - blockTime: now.Add(time.Second), - expectedSchedule: types.ReleaseSchedule{ - Active: true, - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000000)), - ReleasedAmount: sdk.NewCoin(denom, math.ZeroInt()), - LastReleaseTime: now, - EndTime: now.Add(time.Hour * 24 * 365 * 10), + blockTime: now.Add(time.Hour), + expectTransfer: true, + expectedChangeAmount: func(bonded math.LegacyDec) sdk.Coin { + coin, _ := types.CalculateReward(now.Add(time.Hour), now, bonded, defaultParams) + coin.Amount = math.MinInt(coin.Amount, math.NewInt(100000)) + return coin }, - expectedChange: true, - expectedChangeAmount: sdk.NewCoin(denom, math.ZeroInt()), }, { - name: "end time equal to last release time - releases full remaining", - initialSchedule: types.ReleaseSchedule{ - Active: true, - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000)), - ReleasedAmount: sdk.NewCoin(denom, math.NewInt(800)), + name: "pool runs dry - pays remaining balance", + params: defaultParams, + initialPool: types.RewardPool{ + CommunityPool: sdk.NewDecCoins(sdk.NewDecCoin(denom, math.NewInt(10))), LastReleaseTime: now, - EndTime: now, }, - initialPool: sdk.NewDecCoins(sdk.NewDecCoin(denom, math.NewInt(200))), - blockTime: now.Add(time.Hour), - expectedSchedule: types.ReleaseSchedule{ - Active: true, - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000)), - ReleasedAmount: sdk.NewCoin(denom, math.NewInt(1000)), - LastReleaseTime: now.Add(time.Hour), - EndTime: now, + blockTime: now.Add(365 * 24 * time.Hour), + expectTransfer: true, + expectedChangeAmount: func(bonded math.LegacyDec) sdk.Coin { + return sdk.NewCoin(denom, math.NewInt(10)) }, - expectedChange: true, - expectedChangeAmount: sdk.NewCoin(denom, math.NewInt(200)), }, { - name: "sub-second window - partial release without division by zero", - initialSchedule: types.ReleaseSchedule{ - Active: true, - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000)), - ReleasedAmount: sdk.NewCoin(denom, math.ZeroInt()), + name: "bank transfer failure - logs and skips without panic", + params: defaultParams, + initialPool: types.RewardPool{ + // Accounting claims far more than the module account holds + CommunityPool: sdk.NewDecCoins(sdk.NewDecCoin(denom, math.NewInt(1_000_000_000))), LastReleaseTime: now, - EndTime: now.Add(500 * time.Millisecond), // 500ms window - }, - initialPool: sdk.NewDecCoins(sdk.NewDecCoin(denom, math.NewInt(1000))), - blockTime: now.Add(250 * time.Millisecond), // 250ms elapsed = 50% - expectedSchedule: types.ReleaseSchedule{ - Active: true, - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000)), - ReleasedAmount: sdk.NewCoin(denom, math.NewInt(500)), - LastReleaseTime: now.Add(250 * time.Millisecond), - EndTime: now.Add(500 * time.Millisecond), }, - expectedChange: true, - expectedChangeAmount: sdk.NewCoin(denom, math.NewInt(500)), - }, - { - name: "no more distribution - set as inactive", - initialSchedule: types.ReleaseSchedule{ - Active: true, - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000)), - ReleasedAmount: sdk.NewCoin(denom, math.NewInt(1000)), - LastReleaseTime: now.Add(time.Hour), - EndTime: now.Add(time.Hour), - }, - initialPool: sdk.NewDecCoins(sdk.NewDecCoin(denom, math.NewInt(100))), - blockTime: now.Add(time.Hour * 2), - expectedSchedule: types.ReleaseSchedule{ - Active: false, - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000)), - ReleasedAmount: sdk.NewCoin(denom, math.NewInt(1000)), - LastReleaseTime: now.Add(time.Hour), - EndTime: now.Add(time.Hour), - }, - expectedChange: true, - expectedChangeAmount: sdk.NewCoin(denom, math.ZeroInt()), + moduleBankBalance: ptrInt(math.ZeroInt()), + blockTime: now.Add(365 * 24 * time.Hour), + expectTransfer: false, + expectLastReleaseAdv: true, }, } for _, tc := range testCases { suite.Run(tc.name, func() { - // Setup initial state ctx := suite.Ctx.WithBlockTime(tc.blockTime) - // Set initial schedule state - err := suite.App.RewardsKeeper.ReleaseSchedule.Set(ctx, tc.initialSchedule) + err := suite.App.RewardsKeeper.Params.Set(ctx, tc.params) suite.Require().NoError(err) - // Set initial pool state if needed - if !tc.initialPool.Empty() { - err := suite.App.RewardsKeeper.RewardPool.Set(ctx, types.RewardPool{ - CommunityPool: tc.initialPool, - }) - suite.Require().NoError(err) + wantBank := tc.initialPool.CommunityPool.AmountOf(denom).TruncateInt() + if tc.moduleBankBalance != nil { + wantBank = *tc.moduleBankBalance } + suite.resetRewardsModuleBalance(ctx, denom, wantBank) + + err = suite.App.RewardsKeeper.RewardPool.Set(ctx, tc.initialPool) + suite.Require().NoError(err) - // Get initial fee collector balance feeCollectorAddr := suite.App.AccountKeeper.GetModuleAddress("fee_collector") initialFeeCollectorBalance := suite.App.BankKeeper.GetBalance(ctx, feeCollectorAddr, denom) - // Execute BeginBlocker err = suite.App.RewardsKeeper.BeginBlocker(ctx) suite.Require().NoError(err) - if tc.expectedChange { - // Verify schedule state - schedule, err := suite.App.RewardsKeeper.ReleaseSchedule.Get(ctx) - suite.Require().NoError(err) - suite.Require().Equal(tc.expectedSchedule.Active, schedule.Active) - suite.Require().Equal(tc.expectedSchedule.ReleasedAmount, schedule.ReleasedAmount) - suite.Require().Equal(tc.expectedSchedule.TotalAmount, schedule.TotalAmount) - suite.Require().True(tc.expectedSchedule.LastReleaseTime.Equal(schedule.LastReleaseTime)) - suite.Require().True(tc.expectedSchedule.EndTime.Equal(schedule.EndTime)) + pool, err := suite.App.RewardsKeeper.RewardPool.Get(ctx) + suite.Require().NoError(err) + + if tc.expectLastReleaseSet { + suite.Require().True(tc.blockTime.Equal(pool.LastReleaseTime)) + suite.Require().Equal(tc.initialPool.CommunityPool, pool.CommunityPool) + return + } + + if tc.expectLastReleaseAdv { + suite.Require().True(tc.blockTime.Equal(pool.LastReleaseTime)) + suite.Require().True(tc.initialPool.CommunityPool.Equal(pool.CommunityPool)) + return + } + + if !tc.expectTransfer { + suite.Require().True(tc.initialPool.CommunityPool.Equal(pool.CommunityPool)) + suite.Require().True(tc.initialPool.LastReleaseTime.Equal(pool.LastReleaseTime)) + return + } + + bonded, err := suite.App.StakingKeeper.BondedRatio(ctx) + suite.Require().NoError(err) + expected := tc.expectedChangeAmount(bonded) + if expected.IsZero() { + suite.T().Skip("calculated amount is zero for current bonded ratio; skip transfer assertion") + } - // If expecting transfer - if !tc.expectedChangeAmount.IsZero() { - // Verify reward pool deduction - rewardPool, err := suite.App.RewardsKeeper.RewardPool.Get(ctx) - suite.Require().NoError(err) - expectedPool := tc.initialPool.Sub(sdk.NewDecCoinsFromCoins(tc.expectedChangeAmount)) - suite.Require().Equal(expectedPool, rewardPool.CommunityPool) + expectedPool := tc.initialPool.CommunityPool.Sub(sdk.NewDecCoinsFromCoins(expected)) + suite.Require().Equal(expectedPool, pool.CommunityPool) + suite.Require().True(tc.blockTime.Equal(pool.LastReleaseTime)) - // Verify fee collector balance change - currentFeeCollectorBalance := suite.App.BankKeeper.GetBalance(ctx, feeCollectorAddr, denom) - expectedBalance := initialFeeCollectorBalance.Add(tc.expectedChangeAmount) - suite.Require().Equal(expectedBalance, currentFeeCollectorBalance) - } + if !tc.initialPool.TotalReleased.IsNil() && !tc.initialPool.TotalReleased.IsZero() { + suite.Require().Equal(tc.initialPool.TotalReleased.Add(expected), pool.TotalReleased) + } else { + suite.Require().Equal(expected, pool.TotalReleased) } + + currentFeeCollectorBalance := suite.App.BankKeeper.GetBalance(ctx, feeCollectorAddr, denom) + suite.Require().Equal(initialFeeCollectorBalance.Add(expected), currentFeeCollectorBalance) }) } } + +func ptrInt(v math.Int) *math.Int { return &v } + +// resetRewardsModuleBalance drains the rewards module account, then funds it to want. +func (suite *KeeperTestSuite) resetRewardsModuleBalance(ctx sdk.Context, denom string, want math.Int) { + moduleAddr := suite.App.AccountKeeper.GetModuleAddress(types.ModuleName) + cur := suite.App.BankKeeper.GetBalance(ctx, moduleAddr, denom) + if cur.IsPositive() { + err := suite.App.BankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, suite.TestAccs[0], sdk.NewCoins(cur)) + suite.Require().NoError(err) + } + if !want.IsPositive() { + return + } + coin := sdk.NewCoin(denom, want) + suite.FundAcc(suite.TestAccs[0], sdk.NewCoins(coin)) + err := suite.App.BankKeeper.SendCoinsFromAccountToModule(ctx, suite.TestAccs[0], types.ModuleName, sdk.NewCoins(coin)) + suite.Require().NoError(err) +} + +func (suite *KeeperTestSuite) TestGenesisInitExport() { + denom := types.DefaultParams().TokenDenom + params := types.DefaultParams() + params.SupplyBase = math.NewInt(42) + + pool := types.RewardPool{ + CommunityPool: sdk.NewDecCoins(sdk.NewDecCoin(denom, math.NewInt(1000))), + LastReleaseTime: time.Now().UTC(), + TotalReleased: sdk.NewCoin(denom, math.NewInt(5)), + } + + // Fund the module account so InitGenesis accounting check is quiet + err := suite.App.RewardsKeeper.FundCommunityPool(suite.Ctx, sdk.NewCoin(denom, math.NewInt(1000)), suite.TestAccs[0]) + suite.Require().NoError(err) + + suite.App.RewardsKeeper.InitGenesis(suite.Ctx, types.GenesisState{ + Params: params, + RewardPool: pool, + }) + + exported := suite.App.RewardsKeeper.ExportGenesis(suite.Ctx) + suite.Require().Equal(params, exported.Params) + suite.Require().True(pool.CommunityPool.Equal(exported.RewardPool.CommunityPool)) + suite.Require().True(pool.LastReleaseTime.Equal(exported.RewardPool.LastReleaseTime)) + suite.Require().Equal(pool.TotalReleased, exported.RewardPool.TotalReleased) + + // Accounting mismatch is logged, not fatal + badPool := pool + badPool.CommunityPool = badPool.CommunityPool.Add(sdk.NewDecCoin(denom, math.NewInt(1))) + suite.App.RewardsKeeper.InitGenesis(suite.Ctx, types.GenesisState{ + Params: params, + RewardPool: badPool, + }) + suite.Require().Error(suite.App.RewardsKeeper.ValidateModuleAccounting(suite.Ctx)) +} diff --git a/x/rewards/keeper/genesis.go b/x/rewards/keeper/genesis.go index 57e7f6c1..06b3368b 100644 --- a/x/rewards/keeper/genesis.go +++ b/x/rewards/keeper/genesis.go @@ -16,10 +16,6 @@ func (k Keeper) InitGenesis(ctx sdk.Context, data types.GenesisState) { panic(err) } - if err := k.ReleaseSchedule.Set(ctx, data.ReleaseSchedule); err != nil { - panic(err) - } - // Consistency check: warn if the module bank balance cannot cover the // CommunityPool accounting. This is only logged because module InitGenesis // ordering may fund the module account after this runs. @@ -40,10 +36,5 @@ func (k Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState { panic(err) } - releaseSchedule, err := k.ReleaseSchedule.Get(ctx) - if err != nil { - panic(err) - } - - return types.NewGenesisState(params, rewardPool, releaseSchedule) + return types.NewGenesisState(params, rewardPool) } diff --git a/x/rewards/keeper/grpc_query.go b/x/rewards/keeper/grpc_query.go index fdcafd13..14be2986 100644 --- a/x/rewards/keeper/grpc_query.go +++ b/x/rewards/keeper/grpc_query.go @@ -34,12 +34,3 @@ func (k Querier) RewardPool(ctx context.Context, _ *types.QueryRewardPoolRequest } return &types.QueryRewardPoolResponse{RewardPool: pool}, nil } - -// ReleaseSchedule queries the schedule information -func (k Querier) ReleaseSchedule(ctx context.Context, _ *types.QueryReleaseScheduleRequest) (*types.QueryReleaseScheduleResponse, error) { - schedule, err := k.Keeper.ReleaseSchedule.Get(ctx) - if err != nil { - return nil, err - } - return &types.QueryReleaseScheduleResponse{ReleaseSchedule: schedule}, nil -} diff --git a/x/rewards/keeper/grpc_query_test.go b/x/rewards/keeper/grpc_query_test.go index c2fd86c5..8bc91237 100644 --- a/x/rewards/keeper/grpc_query_test.go +++ b/x/rewards/keeper/grpc_query_test.go @@ -10,7 +10,6 @@ import ( ) func (suite *KeeperTestSuite) TestQuerierParams() { - // Set up default params defaultParams := types.DefaultParams() err := suite.App.RewardsKeeper.Params.Set(suite.Ctx, defaultParams) suite.Require().NoError(err) @@ -25,16 +24,14 @@ func (suite *KeeperTestSuite) TestQuerierParams() { { name: "success - get default params", setup: func() { - // Already set up }, expectedPass: true, }, { name: "success - with modified params", setup: func() { - modifiedParams := types.Params{ - TokenDenom: "modified", - } + modifiedParams := types.DefaultParams() + modifiedParams.SupplyBase = math.NewInt(12345) err := suite.App.RewardsKeeper.Params.Set(suite.Ctx, modifiedParams) suite.Require().NoError(err) }, @@ -50,7 +47,6 @@ func (suite *KeeperTestSuite) TestQuerierParams() { if tc.expectedPass { suite.Require().NoError(err) - // Verify returned params match what we expect expectedParams, err := suite.App.RewardsKeeper.Params.Get(suite.Ctx) suite.Require().NoError(err) suite.Require().Equal(expectedParams, res.Params) @@ -62,7 +58,6 @@ func (suite *KeeperTestSuite) TestQuerierParams() { } func (suite *KeeperTestSuite) TestQuerierRewardPool() { - // Initialize an empty pool err := suite.App.RewardsKeeper.RewardPool.Set(suite.Ctx, types.RewardPool{}) suite.Require().NoError(err) @@ -76,14 +71,12 @@ func (suite *KeeperTestSuite) TestQuerierRewardPool() { { name: "success - empty pool", setup: func() { - // Already set up empty pool }, expectedPass: true, }, { name: "success - with funds", setup: func() { - // Fund the pool fundMsg := types.NewMsgFundPool( suite.TestAccs[0], sdk.NewCoin("akii", math.NewInt(100000))) @@ -102,7 +95,6 @@ func (suite *KeeperTestSuite) TestQuerierRewardPool() { if tc.expectedPass { suite.Require().NoError(err) - // Verify returned pool matches what we expect expectedPool, err := suite.App.RewardsKeeper.RewardPool.Get(suite.Ctx) suite.Require().NoError(err) suite.Require().Equal(expectedPool, res.RewardPool) @@ -112,59 +104,3 @@ func (suite *KeeperTestSuite) TestQuerierRewardPool() { }) } } - -func (suite *KeeperTestSuite) TestQuerierReleaseSchedule() { - // Initialize an empty schedule - err := suite.App.RewardsKeeper.ReleaseSchedule.Set(suite.Ctx, types.ReleaseSchedule{}) - suite.Require().NoError(err) - - querier := keeper.NewQuerier(suite.App.RewardsKeeper) - - testCases := []struct { - name string - setup func() - expectedPass bool - }{ - { - name: "success - empty schedule", - setup: func() { - // Already set up empty schedule - }, - expectedPass: true, - }, - { - name: "success - with active schedule", - setup: func() { - // Set up an active schedule - schedule := types.ReleaseSchedule{ - TotalAmount: sdk.NewCoin("akii", math.NewInt(10000)), - ReleasedAmount: sdk.NewCoin("akii", math.NewInt(2000)), - EndTime: suite.Ctx.BlockTime().AddDate(0, 0, 7), // 1 week from now - LastReleaseTime: suite.Ctx.BlockTime(), - Active: true, - } - err := suite.App.RewardsKeeper.ReleaseSchedule.Set(suite.Ctx, schedule) - suite.Require().NoError(err) - }, - expectedPass: true, - }, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - tc.setup() - - res, err := querier.ReleaseSchedule(suite.Ctx, &types.QueryReleaseScheduleRequest{}) - if tc.expectedPass { - suite.Require().NoError(err) - - // Verify returned schedule matches what we expect - expectedSchedule, err := suite.App.RewardsKeeper.ReleaseSchedule.Get(suite.Ctx) - suite.Require().NoError(err) - suite.Require().Equal(expectedSchedule, res.ReleaseSchedule) - } else { - suite.Require().Error(err) - } - }) - } -} diff --git a/x/rewards/keeper/keeper.go b/x/rewards/keeper/keeper.go index 4e4a0be3..1ce4b45b 100644 --- a/x/rewards/keeper/keeper.go +++ b/x/rewards/keeper/keeper.go @@ -18,39 +18,42 @@ type ( Keeper struct { cdc codec.BinaryCodec - bankKeeper types.BankKeeper + storeService store.KVStoreService + bankKeeper types.BankKeeper + stakingKeeper types.StakingKeeper // the address capable of executing a MsgUpdateParams message. Typically, this // should be the x/gov module account. authority string feeCollectorName string // name of the FeeCollector ModuleAccount - Schema collections.Schema - Params collections.Item[types.Params] - RewardPool collections.Item[types.RewardPool] - ReleaseSchedule collections.Item[types.ReleaseSchedule] + Schema collections.Schema + Params collections.Item[types.Params] + RewardPool collections.Item[types.RewardPool] } ) -// NewKeeper returns a new instance of the x/tokenfactory keeper +// NewKeeper returns a new instance of the x/rewards keeper func NewKeeper( cdc codec.BinaryCodec, storeService store.KVStoreService, bankKeeper types.BankKeeper, + stakingKeeper types.StakingKeeper, authority, feeCollectorName string, ) Keeper { sb := collections.NewSchemaBuilder(storeService) k := Keeper{ cdc: cdc, - bankKeeper: bankKeeper, + storeService: storeService, + bankKeeper: bankKeeper, + stakingKeeper: stakingKeeper, authority: authority, feeCollectorName: feeCollectorName, - Params: collections.NewItem(sb, types.ParamsKey, "params", codec.CollValue[types.Params](cdc)), - RewardPool: collections.NewItem(sb, types.RewardPoolKey, "reward_pool", codec.CollValue[types.RewardPool](cdc)), - ReleaseSchedule: collections.NewItem(sb, types.ReleaseScheduleKey, "release_schedule", codec.CollValue[types.ReleaseSchedule](cdc)), + Params: collections.NewItem(sb, types.ParamsKey, "params", codec.CollValue[types.Params](cdc)), + RewardPool: collections.NewItem(sb, types.RewardPoolKey, "reward_pool", codec.CollValue[types.RewardPool](cdc)), } schema, err := sb.Build() @@ -67,6 +70,11 @@ func (k Keeper) GetAuthority() string { return k.authority } +// StoreService returns the module KV store service (used by migrations). +func (k Keeper) StoreService() store.KVStoreService { + return k.storeService +} + // Logger returns a logger for the x/rewards module func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) diff --git a/x/rewards/keeper/msg_server.go b/x/rewards/keeper/msg_server.go index bf6a6961..992a3ea7 100644 --- a/x/rewards/keeper/msg_server.go +++ b/x/rewards/keeper/msg_server.go @@ -16,7 +16,7 @@ type msgServer struct { var _ types.MsgServer = msgServer{} -// NewMsgServerImpl returns an implementation of the distribution MsgServer interface +// NewMsgServerImpl returns an implementation of the rewards MsgServer interface // for the provided Keeper. func NewMsgServerImpl(keeper Keeper) types.MsgServer { return &msgServer{Keeper: keeper} @@ -74,37 +74,3 @@ func (k msgServer) FundPool(ctx context.Context, msg *types.MsgFundPool) (*types return &types.MsgFundPoolResponse{}, nil } - -// ChangeSchedule validates changes to the release scheduler -func (k msgServer) ChangeSchedule(ctx context.Context, msg *types.MsgChangeSchedule) (*types.MsgChangeScheduleResponse, error) { - // Get cosmos sdk context from golang context - sdkCtx := sdk.UnwrapSDKContext(ctx) - - // Authority validation - if err := k.validateAuthority(msg.Authority); err != nil { - return nil, err - } - - // Check if schedule is sound - schedule := msg.Schedule - if err := k.validateSchedule(sdkCtx, schedule); err != nil { - return nil, fmt.Errorf("invalid schedule: %w", err) - } - - // Check available funds - if err := k.fundsAvailable(sdkCtx, schedule.TotalAmount); err != nil { - return nil, fmt.Errorf("insufficient funds: %w", err) - } - - // Save the new schedule - if err := k.ReleaseSchedule.Set(sdkCtx, schedule); err != nil { - return nil, fmt.Errorf("failed to set release schedule: %w", err) - } - - sdkCtx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypeChangeSchedule, - sdk.NewAttribute(types.AttributeKeyTotalAmount, schedule.TotalAmount.String()), - )) - - return &types.MsgChangeScheduleResponse{}, nil -} diff --git a/x/rewards/keeper/msg_server_test.go b/x/rewards/keeper/msg_server_test.go index b10d114b..ef86fd45 100644 --- a/x/rewards/keeper/msg_server_test.go +++ b/x/rewards/keeper/msg_server_test.go @@ -1,8 +1,6 @@ package keeper_test import ( - "time" - "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" @@ -41,6 +39,18 @@ func (suite *KeeperTestSuite) TestUpdateParams() { ), expectedPass: false, }, + { + name: "valid params with supply base", + msg: types.NewMsgUpdateParams( + suite.App.RewardsKeeper.GetAuthority(), + func() types.Params { + p := types.DefaultParams() + p.SupplyBase = math.NewInt(1_000_000) + return p + }(), + ), + expectedPass: true, + }, } for _, tc := range testCases { @@ -49,7 +59,6 @@ func (suite *KeeperTestSuite) TestUpdateParams() { if tc.expectedPass { suite.Require().NoError(err) - // Verify params were updated params, err := suite.App.RewardsKeeper.Params.Get(suite.Ctx) suite.Require().NoError(err) suite.Require().Equal(tc.msg.Params, params) @@ -62,7 +71,6 @@ func (suite *KeeperTestSuite) TestUpdateParams() { // TestFundPool tests funding the pool func (suite *KeeperTestSuite) TestFundPool() { - // Set up default params defaultParams := types.DefaultParams() err := suite.App.RewardsKeeper.Params.Set(suite.Ctx, defaultParams) suite.Require().NoError(err) @@ -101,7 +109,6 @@ func (suite *KeeperTestSuite) TestFundPool() { if tc.expectedPass { suite.Require().NoError(err) - // Verify funds were added to the pool pool, err := suite.App.RewardsKeeper.RewardPool.Get(suite.Ctx) suite.Require().NoError(err) suite.Require().True(pool.CommunityPool.AmountOf(defaultParams.TokenDenom).Equal((math.LegacyNewDecFromBigInt(tc.msg.Amount.Amount.BigInt())))) @@ -111,176 +118,3 @@ func (suite *KeeperTestSuite) TestFundPool() { }) } } - -// TestChangeSchedule tests changes to the release schedule -func (suite *KeeperTestSuite) TestChangeSchedule() { - // Set up default params - defaultParams := types.DefaultParams() - err := suite.App.RewardsKeeper.Params.Set(suite.Ctx, defaultParams) - suite.Require().NoError(err) - - // Fund the pool first - fundMsg := types.NewMsgFundPool( - suite.TestAccs[0], - sdk.NewCoin(defaultParams.TokenDenom, math.NewInt(100000))) - _, err = suite.msgServer.FundPool(suite.Ctx, fundMsg) - suite.Require().NoError(err) - - // Get module authority - authority := suite.App.RewardsKeeper.GetAuthority() - - // Valid base schedule - validEndTime := time.Now().Add(time.Hour * 24) - validSchedule := types.ReleaseSchedule{ - TotalAmount: sdk.NewCoin(defaultParams.TokenDenom, math.NewInt(50000)), - ReleasedAmount: sdk.NewCoin(defaultParams.TokenDenom, math.NewInt(0)), - EndTime: validEndTime, - LastReleaseTime: time.Time{}, - Active: true, - } - - testCases := []struct { - name string - authority string - modifySchedule func(types.ReleaseSchedule) types.ReleaseSchedule - expectedPass bool - }{ - { - name: "valid schedule", - authority: authority, - modifySchedule: func(s types.ReleaseSchedule) types.ReleaseSchedule { - return s // unchanged valid schedule - }, - expectedPass: true, - }, - { - name: "invalid authority", - authority: suite.TestAccs[0].String(), - modifySchedule: func(s types.ReleaseSchedule) types.ReleaseSchedule { - return s // authority is checked before schedule validation - }, - expectedPass: false, - }, - { - name: "invalid denom", - authority: authority, - modifySchedule: func(s types.ReleaseSchedule) types.ReleaseSchedule { - s.TotalAmount.Denom = "invalid" - s.ReleasedAmount.Denom = "invalid" - return s - }, - expectedPass: false, - }, - { - name: "zero total amount", - authority: authority, - modifySchedule: func(s types.ReleaseSchedule) types.ReleaseSchedule { - s.TotalAmount.Amount = math.NewInt(0) - return s - }, - expectedPass: false, - }, - { - name: "negative amount", - authority: authority, - modifySchedule: func(s types.ReleaseSchedule) types.ReleaseSchedule { - s.TotalAmount.Amount = math.NewInt(-100) - return s - }, - expectedPass: false, - }, - { - name: "released exceeds total", - authority: authority, - modifySchedule: func(s types.ReleaseSchedule) types.ReleaseSchedule { - s.ReleasedAmount.Amount = math.NewInt(60000) - return s - }, - expectedPass: false, - }, - { - name: "denom mismatch", - authority: authority, - modifySchedule: func(s types.ReleaseSchedule) types.ReleaseSchedule { - s.ReleasedAmount.Denom = "otherdenom" - return s - }, - expectedPass: false, - }, - { - name: "end time in past", - authority: authority, - modifySchedule: func(s types.ReleaseSchedule) types.ReleaseSchedule { - s.EndTime = time.Now().Add(-time.Hour) - return s - }, - expectedPass: false, - }, - { - name: "last release in future", - authority: authority, - modifySchedule: func(s types.ReleaseSchedule) types.ReleaseSchedule { - s.LastReleaseTime = time.Now().Add(time.Hour) - return s - }, - expectedPass: false, - }, - { - name: "last release after end time", - authority: authority, - modifySchedule: func(s types.ReleaseSchedule) types.ReleaseSchedule { - s.LastReleaseTime = s.EndTime.Add(time.Hour) - return s - }, - expectedPass: false, - }, - { - name: "active with zero end time", - authority: authority, - modifySchedule: func(s types.ReleaseSchedule) types.ReleaseSchedule { - s.EndTime = time.Time{} - return s - }, - expectedPass: false, - }, - { - name: "insufficient funds", - authority: authority, - modifySchedule: func(s types.ReleaseSchedule) types.ReleaseSchedule { - s.TotalAmount.Amount = math.NewInt(200000) // More than we funded - return s - }, - expectedPass: false, - }, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - // Create modified schedule - modifiedSchedule := tc.modifySchedule(validSchedule) - - // Create message - msg := &types.MsgChangeSchedule{ - Authority: tc.authority, - Schedule: modifiedSchedule, - } - - _, err := suite.msgServer.ChangeSchedule(suite.Ctx, msg) - if tc.expectedPass { - suite.Require().NoError(err) - - // Verify schedule was updated - storedSchedule, err := suite.App.RewardsKeeper.ReleaseSchedule.Get(suite.Ctx) - suite.Require().NoError(err) - // Check individually cause times can be utc vs local but same stamp - suite.Require().Equal(modifiedSchedule.Active, storedSchedule.Active) - suite.Require().Equal(modifiedSchedule.ReleasedAmount, storedSchedule.ReleasedAmount) - suite.Require().Equal(modifiedSchedule.TotalAmount, storedSchedule.TotalAmount) - suite.Require().True(modifiedSchedule.LastReleaseTime.Equal(storedSchedule.LastReleaseTime)) - suite.Require().True(modifiedSchedule.EndTime.Equal(storedSchedule.EndTime)) - } else { - suite.Require().Error(err) - } - }) - } -} diff --git a/x/rewards/keeper/validation.go b/x/rewards/keeper/validation.go index 9419fd6f..4b793094 100644 --- a/x/rewards/keeper/validation.go +++ b/x/rewards/keeper/validation.go @@ -1,17 +1,11 @@ package keeper import ( - "context" - "fmt" - "time" - "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" - - "github.com/kiichain/kiichain/v7/x/rewards/types" ) // validateAuthority checks if address authority is valid and same as expected @@ -35,93 +29,3 @@ func validateAmount(amount sdk.Coin) error { return nil } - -// validateEndTime checks if time is in the past -func validateEndTime(ctx sdk.Context, endTime time.Time) error { - if endTime.Before(ctx.BlockTime()) { - return fmt.Errorf("end time %s is not in the future", endTime) - } - - return nil -} - -// fundsAvailable checks if the asked funds are available in the pool -func (k Keeper) fundsAvailable(ctx context.Context, amount sdk.Coin) error { - // Get reward pool - rewardPool, err := k.RewardPool.Get(ctx) - if err != nil { - return err - } - - // Check if it is trying to use more funds than available - poolAmount := rewardPool.CommunityPool.AmountOf(amount.Denom) - if sdk.NewDecCoinFromCoin(amount).Amount.GT(poolAmount) { - return fmt.Errorf("reward pool (%s) has less funds than requested (%s)", poolAmount, amount) - } - - return nil -} - -// validateSchedule checks if the asked funds are available in the pool -func (k Keeper) validateSchedule(ctx sdk.Context, schedule types.ReleaseSchedule) error { - // Validate TotalAmount - if err := validateAmount(schedule.TotalAmount); err != nil { - return fmt.Errorf("invalid total amount: %w", err) - } - - // Validate against module params - params, err := k.Params.Get(ctx) - if err != nil { - return fmt.Errorf("failed to get module params: %w", err) - } - if params.TokenDenom != schedule.TotalAmount.Denom { - return fmt.Errorf("denom %s does not match expected denom: %s", - schedule.TotalAmount.Denom, params.TokenDenom) - } - - // Validate ReleasedAmount - if schedule.ReleasedAmount.Denom != schedule.TotalAmount.Denom { - return fmt.Errorf("released amount denom %s doesn't match total amount denom %s", - schedule.ReleasedAmount.Denom, schedule.TotalAmount.Denom) - } - if !schedule.ReleasedAmount.IsZero() { - if err := validateAmount(schedule.ReleasedAmount); err != nil { - return fmt.Errorf("invalid released amount: %w", err) - } - if schedule.ReleasedAmount.Amount.GT(schedule.TotalAmount.Amount) { - return fmt.Errorf("released amount %s cannot exceed total amount %s", - schedule.ReleasedAmount, schedule.TotalAmount) - } - } - - // Time validations - if schedule.EndTime.IsZero() { - return fmt.Errorf("end time cannot be zero") - } - if err = validateEndTime(ctx, schedule.EndTime); err != nil { - return err - } - - currentTime := ctx.BlockTime() - if !schedule.LastReleaseTime.IsZero() { - if schedule.LastReleaseTime.After(currentTime) { - return fmt.Errorf("last release time %s cannot be in the future", - schedule.LastReleaseTime) - } - if schedule.LastReleaseTime.After(schedule.EndTime) { - return fmt.Errorf("last release time %s cannot be after end time %s", - schedule.LastReleaseTime, schedule.EndTime) - } - } - - // 6. Active state consistency - if schedule.Active { - if schedule.TotalAmount.IsZero() { - return fmt.Errorf("active schedule cannot have zero total amount") - } - if schedule.EndTime.IsZero() { - return fmt.Errorf("active schedule must have an end time") - } - } - return nil -} diff --git a/x/rewards/migrations/v2/migrate.go b/x/rewards/migrations/v2/migrate.go new file mode 100644 index 00000000..1b7f4a96 --- /dev/null +++ b/x/rewards/migrations/v2/migrate.go @@ -0,0 +1,98 @@ +// Package v2 migrates x/rewards from consensus version 1 to 2. +// +// Version 2 replaces ReleaseSchedule emissions with inflation-curve params on +// RewardPool. This migration deletes the obsolete schedule key and backfills +// default inflation params when upgrading an existing chain. +package v2 + +import ( + "cosmossdk.io/collections" + "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kiichain/kiichain/v7/x/rewards/keeper" + "github.com/kiichain/kiichain/v7/x/rewards/types" +) + +// legacyReleaseSchedulePrefix is the collections prefix formerly used by +// ReleaseSchedule (removed in consensus version 2). +var legacyReleaseSchedulePrefix = collections.NewPrefix(2) + +// MigrateStore deletes obsolete ReleaseSchedule state and ensures params / +// reward-pool fields required by the inflation-based emitter are populated. +func MigrateStore(ctx sdk.Context, k keeper.Keeper) error { + logger := ctx.Logger().With("module", "rewards", "migration", "v2") + logger.Info("starting rewards v2 migration") + + if err := deleteLegacyReleaseSchedule(ctx, k); err != nil { + return err + } + + if err := migrateParams(ctx, k); err != nil { + return err + } + + if err := migrateRewardPool(ctx, k); err != nil { + return err + } + + logger.Info("rewards v2 migration complete") + return nil +} + +func deleteLegacyReleaseSchedule(ctx sdk.Context, k keeper.Keeper) error { + store := k.StoreService().OpenKVStore(ctx) + return store.Delete(legacyReleaseSchedulePrefix) +} + +func migrateParams(ctx sdk.Context, k keeper.Keeper) error { + params, err := k.Params.Get(ctx) + if err != nil { + // No params yet; genesis/init will set defaults. + return nil + } + + defaults := types.DefaultParams() + + // v1 only persisted token_denom. Missing/zero goal_bonded means the + // inflation-curve params were never configured — install defaults and keep + // the existing denom. supply_base stays 0 so emissions remain off until gov. + if params.GoalBonded.IsNil() || !params.GoalBonded.IsPositive() { + tokenDenom := params.TokenDenom + if tokenDenom == "" { + tokenDenom = defaults.TokenDenom + } + params = defaults + params.TokenDenom = tokenDenom + params.SupplyBase = math.ZeroInt() + return k.Params.Set(ctx, params) + } + + if params.InflationMin.IsNil() { + params.InflationMin = defaults.InflationMin + } + if params.InflationMax.IsNil() || params.InflationMax.LT(params.InflationMin) { + params.InflationMax = defaults.InflationMax + } + if params.SupplyBase.IsNil() { + params.SupplyBase = math.ZeroInt() + } + if params.InflationRateChange.IsNil() || !params.InflationRateChange.IsPositive() { + params.InflationRateChange = defaults.InflationRateChange + } + return k.Params.Set(ctx, params) +} + +func migrateRewardPool(ctx sdk.Context, k keeper.Keeper) error { + pool, err := k.RewardPool.Get(ctx) + if err != nil { + return nil + } + + if pool.TotalReleased.IsNil() { + pool.TotalReleased = sdk.Coin{} + return k.RewardPool.Set(ctx, pool) + } + return nil +} diff --git a/x/rewards/migrations/v2/migrate_test.go b/x/rewards/migrations/v2/migrate_test.go new file mode 100644 index 00000000..f323e327 --- /dev/null +++ b/x/rewards/migrations/v2/migrate_test.go @@ -0,0 +1,58 @@ +package v2_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" + + "cosmossdk.io/collections" + "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kiichain/kiichain/v7/app/apptesting" + v2 "github.com/kiichain/kiichain/v7/x/rewards/migrations/v2" + "github.com/kiichain/kiichain/v7/x/rewards/types" +) + +type MigrateTestSuite struct { + apptesting.KeeperTestHelper +} + +func TestMigrateTestSuite(t *testing.T) { + suite.Run(t, new(MigrateTestSuite)) +} + +func (suite *MigrateTestSuite) TestMigrateStore() { + suite.Setup() + + k := suite.App.RewardsKeeper + denom := types.DefaultParams().TokenDenom + + // Simulate v1 params (denom only meaningful; other fields empty/zero). + suite.Require().NoError(k.Params.Set(suite.Ctx, types.Params{ + TokenDenom: denom, + })) + suite.Require().NoError(k.RewardPool.Set(suite.Ctx, types.RewardPool{ + CommunityPool: sdk.NewDecCoins(sdk.NewDecCoin(denom, math.NewInt(100))), + })) + + // Write a dummy legacy ReleaseSchedule key under prefix 2. + legacyPrefix := collections.NewPrefix(2) + suite.Require().NoError(k.StoreService().OpenKVStore(suite.Ctx).Set(legacyPrefix, []byte("legacy"))) + + suite.Require().NoError(v2.MigrateStore(suite.Ctx, k)) + + got, err := k.StoreService().OpenKVStore(suite.Ctx).Get(legacyPrefix) + suite.Require().NoError(err) + suite.Require().Nil(got) + + params, err := k.Params.Get(suite.Ctx) + suite.Require().NoError(err) + suite.Require().Equal(denom, params.TokenDenom) + defaults := types.DefaultParams() + suite.Require().True(params.GoalBonded.Equal(defaults.GoalBonded), "got GoalBonded=%s want %s", params.GoalBonded, defaults.GoalBonded) + suite.Require().True(params.InflationMax.Equal(defaults.InflationMax), "got InflationMax=%s want %s", params.InflationMax, defaults.InflationMax) + suite.Require().True(params.InflationRateChange.Equal(defaults.InflationRateChange), "got InflationRateChange=%s want %s", params.InflationRateChange, defaults.InflationRateChange) + suite.Require().True(params.SupplyBase.IsZero()) +} diff --git a/x/rewards/module.go b/x/rewards/module.go index 3d8a934e..411d9fbd 100644 --- a/x/rewards/module.go +++ b/x/rewards/module.go @@ -29,6 +29,7 @@ import ( "github.com/kiichain/kiichain/v7/x/rewards/client/cli" "github.com/kiichain/kiichain/v7/x/rewards/keeper" + v2 "github.com/kiichain/kiichain/v7/x/rewards/migrations/v2" "github.com/kiichain/kiichain/v7/x/rewards/types" ) @@ -40,7 +41,7 @@ var ( ) // ConsensusVersion defines the current x/rewards module consensus version. -const ConsensusVersion = 1 +const ConsensusVersion = 2 // ---------------------------------------------------------------------------- // AppModuleBasic @@ -143,6 +144,13 @@ func (AppModule) QuerierRoute() string { return types.QuerierRoute } func (am AppModule) RegisterServices(cfg module.Configurator) { types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) types.RegisterQueryServer(cfg.QueryServer(), keeper.NewQuerier(am.keeper)) + + // Consensus v1 -> v2: drop ReleaseSchedule state and backfill inflation params. + if err := cfg.RegisterMigration(types.ModuleName, 1, func(ctx sdk.Context) error { + return v2.MigrateStore(ctx, am.keeper) + }); err != nil { + panic(fmt.Errorf("failed to migrate x/%s to v2: %w", types.ModuleName, err)) + } } // InitGenesis performs the x/rewards module's genesis initialization. It diff --git a/x/rewards/types/codec.go b/x/rewards/types/codec.go index 4bb348f1..b7cf207e 100644 --- a/x/rewards/types/codec.go +++ b/x/rewards/types/codec.go @@ -9,12 +9,10 @@ import ( // RegisterInterfaces register interfaces into the app func RegisterInterfaces(registry types.InterfaceRegistry) { - // Register messages registry.RegisterImplementations( (*sdk.Msg)(nil), &MsgUpdateParams{}, &MsgFundPool{}, - &MsgChangeSchedule{}, ) msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) @@ -24,8 +22,6 @@ func RegisterInterfaces(registry types.InterfaceRegistry) { // and concrete types on the provided LegacyAmino codec. These types are used // for Amino JSON serialization. func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { - // Register all your concrete types cdc.RegisterConcrete(&MsgUpdateParams{}, "rewards/update-params", nil) cdc.RegisterConcrete(&MsgFundPool{}, "rewards/fund-pool", nil) - cdc.RegisterConcrete(&MsgChangeSchedule{}, "rewards/change-schedule", nil) } diff --git a/x/rewards/types/codec_test.go b/x/rewards/types/codec_test.go index 438a415e..f732ee7f 100644 --- a/x/rewards/types/codec_test.go +++ b/x/rewards/types/codec_test.go @@ -23,9 +23,8 @@ func (suite *CodecTestSuite) TestRegisterInterfaces() { RegisterInterfaces(registry) impls := registry.ListImplementations(sdk.MsgInterfaceProtoName) - suite.Require().Equal(3, len(impls)) + suite.Require().Equal(2, len(impls)) suite.Require().ElementsMatch([]string{ - "/kiichain.rewards.v1beta1.MsgChangeSchedule", "/kiichain.rewards.v1beta1.MsgFundPool", "/kiichain.rewards.v1beta1.MsgUpdateParams", }, impls) diff --git a/x/rewards/types/events.go b/x/rewards/types/events.go index ac37893e..9254915d 100644 --- a/x/rewards/types/events.go +++ b/x/rewards/types/events.go @@ -3,11 +3,11 @@ package types const ( EventTypeUpdateParams = "update_params" EventTypeFundPool = "fund_pool" - EventTypeChangeSchedule = "change_schedule" EventTypeRewardDistributed = "reward_distributed" AttributeKeySender = "sender" AttributeKeyAmount = "amount" AttributeKeyTotalReleased = "total_released" - AttributeKeyTotalAmount = "total_amount" + AttributeKeyInflationRate = "inflation_rate" + AttributeKeyBondedRatio = "bonded_ratio" ) diff --git a/x/rewards/types/expected_keepers.go b/x/rewards/types/expected_keepers.go index 7e3dba1d..28ba9562 100644 --- a/x/rewards/types/expected_keepers.go +++ b/x/rewards/types/expected_keepers.go @@ -3,13 +3,19 @@ package types import ( context "context" + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" ) // BankKeeper is used to send and receive coins into module account type BankKeeper interface { - // Methods imported from bank should be defined here SendCoinsFromModuleToModule(ctx context.Context, senderModule, recipientModule string, amt sdk.Coins) error SendCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error GetBalance(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin } + +// StakingKeeper provides the bonded ratio used to drive the emission curve. +type StakingKeeper interface { + BondedRatio(ctx context.Context) (math.LegacyDec, error) +} diff --git a/x/rewards/types/genesis.go b/x/rewards/types/genesis.go index 3a7bcfd1..ad67cd7f 100644 --- a/x/rewards/types/genesis.go +++ b/x/rewards/types/genesis.go @@ -3,22 +3,18 @@ package types import "fmt" // NewGenesisState constructs a genesis state -func NewGenesisState( - params Params, rp RewardPool, release ReleaseSchedule, -) *GenesisState { +func NewGenesisState(params Params, rp RewardPool) *GenesisState { return &GenesisState{ - Params: params, - RewardPool: rp, - ReleaseSchedule: release, + Params: params, + RewardPool: rp, } } // DefaultGenesisState returns the default genesis state of rewards. func DefaultGenesisState() *GenesisState { return &GenesisState{ - RewardPool: InitialRewardPool(), - Params: DefaultParams(), - ReleaseSchedule: InitialReleaseSchedule(), + RewardPool: InitialRewardPool(), + Params: DefaultParams(), } } @@ -32,11 +28,6 @@ func (gs *GenesisState) Validate() error { return err } - if err := gs.ReleaseSchedule.ValidateGenesis(); err != nil { - return err - } - - // Enforce denom consistency: all pool coins and the active schedule must use TokenDenom. tokenDenom := gs.Params.TokenDenom for _, coin := range gs.RewardPool.CommunityPool { if coin.Denom != tokenDenom { @@ -45,9 +36,10 @@ func (gs *GenesisState) Validate() error { } } - if gs.ReleaseSchedule.Active && gs.ReleaseSchedule.TotalAmount.Denom != tokenDenom { - return fmt.Errorf("release schedule total amount denom %s does not match token denom %s", - gs.ReleaseSchedule.TotalAmount.Denom, tokenDenom) + if !gs.RewardPool.TotalReleased.IsNil() && !gs.RewardPool.TotalReleased.IsZero() && + gs.RewardPool.TotalReleased.Denom != tokenDenom { + return fmt.Errorf("total released denom %s does not match token denom %s", + gs.RewardPool.TotalReleased.Denom, tokenDenom) } return nil diff --git a/x/rewards/types/genesis.pb.go b/x/rewards/types/genesis.pb.go index 18e25f04..e08412a3 100644 --- a/x/rewards/types/genesis.pb.go +++ b/x/rewards/types/genesis.pb.go @@ -5,7 +5,6 @@ package types import ( fmt "fmt" - _ "github.com/cosmos/cosmos-sdk/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" io "io" @@ -28,8 +27,6 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type GenesisState struct { // params defines the parameters of the module. Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` - // release_schedule has information of how the reward is being released - ReleaseSchedule ReleaseSchedule `protobuf:"bytes,2,opt,name=release_schedule,json=releaseSchedule,proto3" json:"release_schedule"` // reward_pool has information on the community pool RewardPool RewardPool `protobuf:"bytes,3,opt,name=reward_pool,json=rewardPool,proto3" json:"reward_pool"` } @@ -74,13 +71,6 @@ func (m *GenesisState) GetParams() Params { return Params{} } -func (m *GenesisState) GetReleaseSchedule() ReleaseSchedule { - if m != nil { - return m.ReleaseSchedule - } - return ReleaseSchedule{} -} - func (m *GenesisState) GetRewardPool() RewardPool { if m != nil { return m.RewardPool @@ -97,26 +87,24 @@ func init() { } var fileDescriptor_96ab53dc25b7c542 = []byte{ - // 296 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x90, 0xbf, 0x4e, 0xc3, 0x30, - 0x10, 0xc6, 0x63, 0x40, 0x1d, 0x5c, 0x24, 0x50, 0xc4, 0x10, 0x75, 0x30, 0x15, 0x2a, 0x08, 0x24, - 0x64, 0xab, 0xb0, 0x33, 0x30, 0xc0, 0xc0, 0x52, 0xb5, 0x5b, 0x97, 0xca, 0x49, 0x4f, 0xa9, 0x45, - 0x9a, 0x8b, 0x6c, 0x97, 0x3f, 0x6f, 0xc1, 0x63, 0x75, 0xec, 0xc8, 0x84, 0x50, 0xf2, 0x10, 0xac, - 0xa8, 0x8e, 0x09, 0x62, 0x08, 0x9b, 0x73, 0xf9, 0x7d, 0xbf, 0x3b, 0x7d, 0xf4, 0xec, 0x51, 0xa9, - 0x64, 0x21, 0x55, 0x2e, 0x34, 0x3c, 0x4b, 0x3d, 0x37, 0xe2, 0x69, 0x18, 0x83, 0x95, 0x43, 0x91, - 0x42, 0x0e, 0x46, 0x19, 0x5e, 0x68, 0xb4, 0x18, 0x46, 0x3f, 0x1c, 0xf7, 0x1c, 0xf7, 0x5c, 0xef, - 0x28, 0xc5, 0x14, 0x1d, 0x24, 0xb6, 0xaf, 0x9a, 0xef, 0xb1, 0x04, 0xcd, 0x12, 0x8d, 0x88, 0xa5, - 0x81, 0x46, 0x99, 0xa0, 0xca, 0xfd, 0xff, 0xd3, 0xd6, 0xbd, 0x85, 0xd4, 0x72, 0xe9, 0xd7, 0xf6, - 0x06, 0xad, 0x98, 0x7d, 0x2d, 0xc0, 0x53, 0x27, 0x5f, 0x84, 0xee, 0xdf, 0xd7, 0xe7, 0x4e, 0xac, - 0xb4, 0x10, 0xde, 0xd0, 0x4e, 0xad, 0x89, 0x48, 0x9f, 0x9c, 0x77, 0xaf, 0xfa, 0xbc, 0xed, 0x7c, - 0x3e, 0x72, 0xdc, 0xed, 0xde, 0xfa, 0xe3, 0x38, 0x18, 0xfb, 0x54, 0x38, 0xa5, 0x87, 0x1a, 0x32, - 0x90, 0x06, 0x66, 0x26, 0x59, 0xc0, 0x7c, 0x95, 0x41, 0xb4, 0xe3, 0x4c, 0x17, 0xed, 0xa6, 0x71, - 0x9d, 0x98, 0xf8, 0x80, 0x57, 0x1e, 0xe8, 0xbf, 0xe3, 0xf0, 0x81, 0x76, 0xeb, 0xe4, 0xac, 0x40, - 0xcc, 0xa2, 0x5d, 0xa7, 0x1d, 0xfc, 0xa7, 0xdd, 0x7e, 0x8f, 0x10, 0x33, 0x6f, 0xa4, 0xfa, 0x77, - 0x72, 0xb7, 0x2e, 0x19, 0xd9, 0x94, 0x8c, 0x7c, 0x96, 0x8c, 0xbc, 0x55, 0x2c, 0xd8, 0x54, 0x2c, - 0x78, 0xaf, 0x58, 0x30, 0xbd, 0x4c, 0x95, 0x5d, 0xac, 0x62, 0x9e, 0xe0, 0x52, 0x34, 0x25, 0x36, - 0x8f, 0x97, 0xa6, 0x4f, 0xd7, 0x63, 0xdc, 0x71, 0x45, 0x5e, 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, - 0x22, 0xbd, 0xae, 0x98, 0x0f, 0x02, 0x00, 0x00, + // 264 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xcb, 0xce, 0xcc, 0x4c, + 0xce, 0x48, 0xcc, 0xcc, 0xd3, 0x2f, 0x4a, 0x2d, 0x4f, 0x2c, 0x4a, 0x29, 0xd6, 0x2f, 0x33, 0x4c, + 0x4a, 0x2d, 0x49, 0x34, 0xd4, 0x4f, 0x4f, 0xcd, 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x2b, 0x28, 0xca, + 0x2f, 0xc9, 0x17, 0x92, 0x80, 0xa9, 0xd3, 0x83, 0xaa, 0xd3, 0x83, 0xaa, 0x93, 0x12, 0x49, 0xcf, + 0x4f, 0xcf, 0x07, 0x2b, 0xd2, 0x07, 0xb1, 0x20, 0xea, 0xa5, 0x54, 0x71, 0x9a, 0x5b, 0x90, 0x58, + 0x94, 0x98, 0x0b, 0x35, 0x56, 0x4a, 0x05, 0xa7, 0xb2, 0x92, 0xca, 0x82, 0x54, 0xa8, 0x2a, 0xa5, + 0xcd, 0x8c, 0x5c, 0x3c, 0xee, 0x10, 0xe7, 0x04, 0x97, 0x24, 0x96, 0xa4, 0x0a, 0xd9, 0x71, 0xb1, + 0x41, 0x8c, 0x91, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x36, 0x52, 0xd0, 0xc3, 0xe5, 0x3c, 0xbd, 0x00, + 0xb0, 0x3a, 0x27, 0x96, 0x13, 0xf7, 0xe4, 0x19, 0x82, 0xa0, 0xba, 0x84, 0xbc, 0xb9, 0xb8, 0x21, + 0xea, 0xe2, 0x0b, 0xf2, 0xf3, 0x73, 0x24, 0x98, 0xc1, 0x86, 0xa8, 0xe0, 0x36, 0x24, 0x08, 0xcc, + 0x0f, 0xc8, 0xcf, 0xcf, 0x81, 0x1a, 0xc4, 0x55, 0x04, 0x17, 0xf1, 0x62, 0xe1, 0x60, 0x12, 0x60, + 0x0e, 0x12, 0x28, 0x4a, 0xcd, 0x49, 0x4d, 0x2c, 0x4e, 0x8d, 0x2f, 0x4e, 0xce, 0x48, 0x4d, 0x29, + 0xcd, 0x49, 0x75, 0x72, 0x3b, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, + 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x28, 0x9d, + 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, 0x78, 0x00, 0xc0, 0x19, 0x15, + 0xf0, 0xb0, 0x00, 0x87, 0x41, 0x12, 0x1b, 0x38, 0x10, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, + 0x7a, 0xab, 0x23, 0xc8, 0xab, 0x01, 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -149,16 +137,6 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { } i-- dAtA[i] = 0x1a - { - size, err := m.ReleaseSchedule.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenesis(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 { size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) if err != nil { @@ -191,8 +169,6 @@ func (m *GenesisState) Size() (n int) { _ = l l = m.Params.Size() n += 1 + l + sovGenesis(uint64(l)) - l = m.ReleaseSchedule.Size() - n += 1 + l + sovGenesis(uint64(l)) l = m.RewardPool.Size() n += 1 + l + sovGenesis(uint64(l)) return n @@ -266,39 +242,6 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ReleaseSchedule", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenesis - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenesis - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenesis - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.ReleaseSchedule.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field RewardPool", wireType) diff --git a/x/rewards/types/genesis_test.go b/x/rewards/types/genesis_test.go index 958f9791..525d4a37 100644 --- a/x/rewards/types/genesis_test.go +++ b/x/rewards/types/genesis_test.go @@ -2,7 +2,6 @@ package types_test import ( "testing" - "time" "github.com/stretchr/testify/suite" @@ -22,17 +21,13 @@ func TestGenesisTestSuite(t *testing.T) { } func (suite *GenesisTestSuite) TestNewGenesisState() { - // Create test data params := types.DefaultParams() pool := types.InitialRewardPool() - schedule := types.InitialReleaseSchedule() - // Test creation - genesis := types.NewGenesisState(params, pool, schedule) + genesis := types.NewGenesisState(params, pool) suite.Require().Equal(params, genesis.Params) suite.Require().Equal(pool, genesis.RewardPool) - suite.Require().Equal(schedule, genesis.ReleaseSchedule) } func (suite *GenesisTestSuite) TestDefaultGenesisState() { @@ -40,20 +35,9 @@ func (suite *GenesisTestSuite) TestDefaultGenesisState() { suite.Require().Equal(types.DefaultParams(), defaultGenesis.Params) suite.Require().Equal(types.InitialRewardPool(), defaultGenesis.RewardPool) - suite.Require().Equal(types.InitialReleaseSchedule(), defaultGenesis.ReleaseSchedule) } func (suite *GenesisTestSuite) TestValidateGenesis() { - validParams := types.DefaultParams() - validPool := types.InitialRewardPool() - validSchedule := types.ReleaseSchedule{ - TotalAmount: sdk.NewCoin("akii", math.NewInt(1000)), - ReleasedAmount: sdk.NewCoin("akii", math.NewInt(0)), - EndTime: time.Now().Add(time.Hour * 24), - LastReleaseTime: time.Time{}, - Active: true, - } - testCases := []struct { name string modifyFn func(*types.GenesisState) @@ -64,31 +48,10 @@ func (suite *GenesisTestSuite) TestValidateGenesis() { modifyFn: func(gs *types.GenesisState) {}, expectedPass: true, }, - { - name: "custom valid genesis", - modifyFn: func(gs *types.GenesisState) { - gs.Params = validParams - gs.RewardPool = validPool - gs.ReleaseSchedule = validSchedule - }, - expectedPass: true, - }, { name: "invalid params", modifyFn: func(gs *types.GenesisState) { - gs.Params.TokenDenom = "" // invalid empty denom - }, - expectedPass: false, - }, - { - name: "active schedule with mismatched total amount denom", - modifyFn: func(gs *types.GenesisState) { - gs.ReleaseSchedule = types.ReleaseSchedule{ - TotalAmount: sdk.NewCoin("notkii", math.NewInt(1000)), - ReleasedAmount: sdk.NewCoin("notkii", math.NewInt(0)), - EndTime: time.Now().Add(time.Hour * 24), - Active: true, - } + gs.Params.TokenDenom = "" }, expectedPass: false, }, @@ -104,21 +67,23 @@ func (suite *GenesisTestSuite) TestValidateGenesis() { expectedPass: false, }, { - name: "both active schedule and pool with mismatched denoms", + name: "total released with foreign denom", + modifyFn: func(gs *types.GenesisState) { + gs.RewardPool.TotalReleased = sdk.NewCoin("notkii", math.NewInt(100)) + }, + expectedPass: false, + }, + { + name: "valid funded pool", modifyFn: func(gs *types.GenesisState) { - gs.ReleaseSchedule = types.ReleaseSchedule{ - TotalAmount: sdk.NewCoin("notkii", math.NewInt(1000)), - ReleasedAmount: sdk.NewCoin("notkii", math.NewInt(0)), - EndTime: time.Now().Add(time.Hour * 24), - Active: true, - } gs.RewardPool = types.RewardPool{ CommunityPool: sdk.DecCoins{ - {Denom: "notkii", Amount: math.LegacyNewDec(100)}, + {Denom: "akii", Amount: math.LegacyNewDec(100)}, }, + TotalReleased: sdk.NewCoin("akii", math.NewInt(50)), } }, - expectedPass: false, + expectedPass: true, }, } diff --git a/x/rewards/types/keys.go b/x/rewards/types/keys.go index fd9f90f9..e50968a1 100644 --- a/x/rewards/types/keys.go +++ b/x/rewards/types/keys.go @@ -3,9 +3,8 @@ package types import "cosmossdk.io/collections" var ( - ParamsKey = collections.NewPrefix(0) - RewardPoolKey = collections.NewPrefix(1) - ReleaseScheduleKey = collections.NewPrefix(2) + ParamsKey = collections.NewPrefix(0) + RewardPoolKey = collections.NewPrefix(1) ) const ( diff --git a/x/rewards/types/msg.go b/x/rewards/types/msg.go index cee5712f..a2b8be7f 100644 --- a/x/rewards/types/msg.go +++ b/x/rewards/types/msg.go @@ -8,7 +8,6 @@ import ( var ( _ sdk.Msg = (*MsgUpdateParams)(nil) _ sdk.Msg = (*MsgFundPool)(nil) - _ sdk.Msg = (*MsgChangeSchedule)(nil) ) // NewMsgUpdateParams returns a new MsgUpdateParams with the authority @@ -28,12 +27,3 @@ func NewMsgFundPool(sender sdk.AccAddress, amount sdk.Coin) *MsgFundPool { Amount: amount, } } - -// NewMsgChangeSchedule returns a new MsgChangeSchedule with the authority, -// and a new schedule. -func NewMsgChangeSchedule(authority string, schedule ReleaseSchedule) *MsgChangeSchedule { - return &MsgChangeSchedule{ - Authority: authority, - Schedule: schedule, - } -} diff --git a/x/rewards/types/params.go b/x/rewards/types/params.go index 3e492c7f..148495f1 100644 --- a/x/rewards/types/params.go +++ b/x/rewards/types/params.go @@ -1,19 +1,54 @@ package types import ( + "fmt" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/kiichain/kiichain/v7/app/params" ) -// DefaultParams returns default rewards parameters +// DefaultParams returns default rewards parameters. +// SupplyBase defaults to zero => zero emissions until governance sets it. +// SupplyBase is a notional emission-scale base (not chain total supply). func DefaultParams() Params { return Params{ - TokenDenom: params.BaseDenom, // akii base denom + TokenDenom: params.BaseDenom, // akii + GoalBonded: math.LegacyNewDecWithPrec(67, 2), // 0.67 + InflationMin: math.LegacyZeroDec(), // 0.00 + InflationMax: math.LegacyNewDecWithPrec(20, 2), // 0.20 + SupplyBase: math.ZeroInt(), // TBD via governance + InflationRateChange: math.LegacyNewDecWithPrec(13, 2), // 0.13 } } -// ValidateBasic performs basic validation on distribution parameters. +// ValidateBasic performs basic validation on rewards parameters. func (p Params) ValidateBasic() error { - return sdk.ValidateDenom(p.TokenDenom) + if err := sdk.ValidateDenom(p.TokenDenom); err != nil { + return err + } + + if !p.GoalBonded.IsPositive() || p.GoalBonded.GT(math.LegacyOneDec()) { + return fmt.Errorf("goalBonded must be in (0,1], got %s", p.GoalBonded) + } + + if p.InflationMin.IsNegative() { + return fmt.Errorf("inflationMin cannot be negative, got %s", p.InflationMin) + } + + if p.InflationMax.LT(p.InflationMin) { + return fmt.Errorf("inflationMax %s < inflationMin %s", p.InflationMax, p.InflationMin) + } + + if p.SupplyBase.IsNegative() { + return fmt.Errorf("supplyBase cannot be negative, got %s", p.SupplyBase) + } + + if !p.InflationRateChange.IsPositive() || p.InflationRateChange.GT(math.LegacyOneDec()) { + return fmt.Errorf("inflationRateChange must be in (0,1], got %s", p.InflationRateChange) + } + + return nil } diff --git a/x/rewards/types/params.pb.go b/x/rewards/types/params.pb.go index 84166d10..9c17afe2 100644 --- a/x/rewards/types/params.pb.go +++ b/x/rewards/types/params.pb.go @@ -4,6 +4,7 @@ package types import ( + cosmossdk_io_math "cosmossdk.io/math" fmt "fmt" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" @@ -25,8 +26,20 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // Params defines the parameters for the rewards module. type Params struct { - // Denom used + // Denom used for emissions TokenDenom string `protobuf:"bytes,1,opt,name=token_denom,json=tokenDenom,proto3" json:"token_denom,omitempty"` + // Target fraction of supply staked (e.g. "0.67") + GoalBonded cosmossdk_io_math.LegacyDec `protobuf:"bytes,2,opt,name=goal_bonded,json=goalBonded,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"goal_bonded"` + // Floor on the emission rate (e.g. "0.00") + InflationMin cosmossdk_io_math.LegacyDec `protobuf:"bytes,3,opt,name=inflation_min,json=inflationMin,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"inflation_min"` + // Ceiling on the emission rate (e.g. "0.20") + InflationMax cosmossdk_io_math.LegacyDec `protobuf:"bytes,4,opt,name=inflation_max,json=inflationMax,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"inflation_max"` + // Notional base (in token base units) used to size annual provisions: + // annual_provision = inflation * supply_base. This is NOT chain total supply; + // it is a governance knob for emission scale. Zero disables emissions. + SupplyBase cosmossdk_io_math.Int `protobuf:"bytes,5,opt,name=supply_base,json=supplyBase,proto3,customtype=cosmossdk.io/math.Int" json:"supply_base"` + // Steepness of the emission curve (e.g. "0.13") + InflationRateChange cosmossdk_io_math.LegacyDec `protobuf:"bytes,6,opt,name=inflation_rate_change,json=inflationRateChange,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"inflation_rate_change"` } func (m *Params) Reset() { *m = Params{} } @@ -78,19 +91,29 @@ func init() { } var fileDescriptor_54abd846c753e163 = []byte{ - // 186 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xcd, 0xce, 0xcc, 0x4c, - 0xce, 0x48, 0xcc, 0xcc, 0xd3, 0x2f, 0x4a, 0x2d, 0x4f, 0x2c, 0x4a, 0x29, 0xd6, 0x2f, 0x33, 0x4c, - 0x4a, 0x2d, 0x49, 0x34, 0xd4, 0x2f, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, - 0xc9, 0x17, 0x92, 0x80, 0x29, 0xd3, 0x83, 0x2a, 0xd3, 0x83, 0x2a, 0x93, 0x12, 0x49, 0xcf, 0x4f, - 0xcf, 0x07, 0x2b, 0xd2, 0x07, 0xb1, 0x20, 0xea, 0xa5, 0x54, 0x70, 0x1a, 0x5b, 0x52, 0x59, 0x90, - 0x0a, 0x35, 0x55, 0x49, 0x93, 0x8b, 0x2d, 0x00, 0x6c, 0x8b, 0x90, 0x3c, 0x17, 0x77, 0x49, 0x7e, - 0x76, 0x6a, 0x5e, 0x7c, 0x4a, 0x6a, 0x5e, 0x7e, 0xae, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x67, 0x10, - 0x17, 0x58, 0xc8, 0x05, 0x24, 0xe2, 0xe4, 0x76, 0xe2, 0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, 0x8c, - 0x0f, 0x1e, 0xc9, 0x31, 0x4e, 0x78, 0x2c, 0xc7, 0x70, 0xe1, 0xb1, 0x1c, 0xc3, 0x8d, 0xc7, 0x72, - 0x0c, 0x51, 0x3a, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, 0xfa, 0x70, 0x5b, - 0xe1, 0x8c, 0x0a, 0xb8, 0x03, 0xc0, 0x16, 0x27, 0xb1, 0x81, 0x6d, 0x36, 0x06, 0x04, 0x00, 0x00, - 0xff, 0xff, 0x46, 0xe3, 0xb3, 0x7a, 0xf8, 0x00, 0x00, 0x00, + // 338 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x92, 0xc1, 0x4a, 0xc3, 0x40, + 0x10, 0x40, 0x13, 0xab, 0x05, 0xb7, 0x7a, 0x89, 0x16, 0x16, 0xc5, 0x54, 0x14, 0xc1, 0x83, 0x24, + 0x14, 0xef, 0x1e, 0x62, 0x11, 0x05, 0x05, 0xc9, 0x45, 0xf0, 0x12, 0x26, 0xc9, 0x9a, 0x2c, 0x6d, + 0x76, 0x42, 0x76, 0xab, 0xed, 0x5f, 0xf8, 0x01, 0x7e, 0x50, 0x8f, 0x3d, 0x8a, 0x87, 0x22, 0xed, + 0x8f, 0x48, 0xb6, 0x31, 0x82, 0x5e, 0x8a, 0xb7, 0x65, 0x78, 0xef, 0x31, 0xb0, 0x43, 0x4e, 0xfa, + 0x9c, 0x47, 0x29, 0x70, 0xe1, 0x16, 0xec, 0x05, 0x8a, 0x58, 0xba, 0xcf, 0xdd, 0x90, 0x29, 0xe8, + 0xba, 0x39, 0x14, 0x90, 0x49, 0x27, 0x2f, 0x50, 0xa1, 0x45, 0xbf, 0x31, 0xa7, 0xc2, 0x9c, 0x0a, + 0xdb, 0xdb, 0x4d, 0x30, 0x41, 0x0d, 0xb9, 0xe5, 0x6b, 0xc9, 0x1f, 0xbd, 0x35, 0x48, 0xf3, 0x5e, + 0x07, 0xac, 0x0e, 0x69, 0x29, 0xec, 0x33, 0x11, 0xc4, 0x4c, 0x60, 0x46, 0xcd, 0x43, 0xf3, 0x74, + 0xd3, 0x27, 0x7a, 0xd4, 0x2b, 0x27, 0x56, 0x8f, 0xb4, 0x12, 0x84, 0x41, 0x10, 0xa2, 0x88, 0x59, + 0x4c, 0xd7, 0x4a, 0xc0, 0x3b, 0x9e, 0xcc, 0x3a, 0xc6, 0xc7, 0xac, 0xb3, 0x1f, 0xa1, 0xcc, 0x50, + 0xca, 0xb8, 0xef, 0x70, 0x74, 0x33, 0x50, 0xa9, 0x73, 0xcb, 0x12, 0x88, 0xc6, 0x3d, 0x16, 0xf9, + 0xa4, 0xf4, 0x3c, 0xad, 0x59, 0xd7, 0x64, 0x9b, 0x8b, 0xa7, 0x01, 0x28, 0x8e, 0x22, 0xc8, 0xb8, + 0xa0, 0x8d, 0xd5, 0x3b, 0x5b, 0xb5, 0x79, 0xc7, 0xc5, 0xaf, 0x12, 0x8c, 0xe8, 0xfa, 0xbf, 0x4a, + 0x30, 0xb2, 0x2e, 0x48, 0x4b, 0x0e, 0xf3, 0x7c, 0x30, 0x0e, 0x42, 0x90, 0x8c, 0x6e, 0xe8, 0xce, + 0x41, 0xd5, 0x69, 0xff, 0xed, 0xdc, 0x08, 0xe5, 0x93, 0xa5, 0xe1, 0x81, 0x64, 0xd6, 0x03, 0x69, + 0xff, 0x6c, 0x52, 0x80, 0x62, 0x41, 0x94, 0x82, 0x48, 0x18, 0x6d, 0xae, 0xbe, 0xd1, 0x4e, 0x5d, + 0xf0, 0x41, 0xb1, 0x4b, 0xed, 0x7b, 0x57, 0x93, 0xb9, 0x6d, 0x4e, 0xe7, 0xb6, 0xf9, 0x39, 0xb7, + 0xcd, 0xd7, 0x85, 0x6d, 0x4c, 0x17, 0xb6, 0xf1, 0xbe, 0xb0, 0x8d, 0xc7, 0xb3, 0x84, 0xab, 0x74, + 0x18, 0x3a, 0x11, 0x66, 0x6e, 0x7d, 0x1a, 0xf5, 0x63, 0x54, 0x5f, 0x89, 0x1a, 0xe7, 0x4c, 0x86, + 0x4d, 0xfd, 0xdb, 0xe7, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x20, 0x61, 0xad, 0x45, 0x46, 0x02, + 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -113,6 +136,56 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + { + size := m.InflationRateChange.Size() + i -= size + if _, err := m.InflationRateChange.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + { + size := m.SupplyBase.Size() + i -= size + if _, err := m.SupplyBase.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + { + size := m.InflationMax.Size() + i -= size + if _, err := m.InflationMax.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + { + size := m.InflationMin.Size() + i -= size + if _, err := m.InflationMin.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + { + size := m.GoalBonded.Size() + i -= size + if _, err := m.GoalBonded.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 if len(m.TokenDenom) > 0 { i -= len(m.TokenDenom) copy(dAtA[i:], m.TokenDenom) @@ -144,6 +217,16 @@ func (m *Params) Size() (n int) { if l > 0 { n += 1 + l + sovParams(uint64(l)) } + l = m.GoalBonded.Size() + n += 1 + l + sovParams(uint64(l)) + l = m.InflationMin.Size() + n += 1 + l + sovParams(uint64(l)) + l = m.InflationMax.Size() + n += 1 + l + sovParams(uint64(l)) + l = m.SupplyBase.Size() + n += 1 + l + sovParams(uint64(l)) + l = m.InflationRateChange.Size() + n += 1 + l + sovParams(uint64(l)) return n } @@ -214,6 +297,176 @@ func (m *Params) Unmarshal(dAtA []byte) error { } m.TokenDenom = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GoalBonded", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.GoalBonded.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InflationMin", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.InflationMin.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InflationMax", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.InflationMax.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SupplyBase", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.SupplyBase.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InflationRateChange", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.InflationRateChange.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) diff --git a/x/rewards/types/params_test.go b/x/rewards/types/params_test.go index 4c7c964f..a0f83abc 100644 --- a/x/rewards/types/params_test.go +++ b/x/rewards/types/params_test.go @@ -5,72 +5,104 @@ import ( "github.com/stretchr/testify/require" + "cosmossdk.io/math" + "github.com/kiichain/kiichain/v7/x/rewards/types" ) func TestParamsValidateBasic(t *testing.T) { - type fields struct { - GovernanceMinDeposit string - TokenDenom string - } + valid := types.DefaultParams() + tests := []struct { name string - fields fields + mutate func(p *types.Params) wantErr bool }{ { - name: "success - valid params", - fields: fields{ - TokenDenom: "akii", - }, + name: "success - default params", + mutate: func(p *types.Params) {}, wantErr: false, }, { name: "invalid - empty token denom", - fields: fields{ - TokenDenom: "", + mutate: func(p *types.Params) { + p.TokenDenom = "" + }, + wantErr: true, + }, + { + name: "invalid - goal bonded zero", + mutate: func(p *types.Params) { + p.GoalBonded = math.LegacyZeroDec() + }, + wantErr: true, + }, + { + name: "invalid - goal bonded above one", + mutate: func(p *types.Params) { + p.GoalBonded = math.LegacyNewDecWithPrec(101, 2) }, wantErr: true, }, { - name: "invalid - denom with invalid characters", - fields: fields{ - TokenDenom: "ak ii", + name: "invalid - inflation min negative", + mutate: func(p *types.Params) { + p.InflationMin = math.LegacyNewDec(-1) }, wantErr: true, }, { - name: "invalid - denom too short", - fields: fields{ - TokenDenom: "ak", + name: "invalid - inflation max below min", + mutate: func(p *types.Params) { + p.InflationMin = math.LegacyNewDecWithPrec(10, 2) + p.InflationMax = math.LegacyNewDecWithPrec(5, 2) }, wantErr: true, }, { - name: "invalid - denom starting with a number", - fields: fields{ - TokenDenom: "1akii", + name: "invalid - supply base negative", + mutate: func(p *types.Params) { + p.SupplyBase = math.NewInt(-1) + }, + wantErr: true, + }, + { + name: "invalid - inflation rate change zero", + mutate: func(p *types.Params) { + p.InflationRateChange = math.LegacyZeroDec() + }, + wantErr: true, + }, + { + name: "invalid - inflation rate change above one", + mutate: func(p *types.Params) { + p.InflationRateChange = math.LegacyNewDecWithPrec(101, 2) }, wantErr: true, }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - p := types.Params{ - TokenDenom: tt.fields.TokenDenom, - } - if err := p.ValidateBasic(); (err != nil) != tt.wantErr { - t.Errorf("ValidateBasic() error = %v, wantErr %v", err, tt.wantErr) + p := valid + tt.mutate(&p) + err := p.ValidateBasic() + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) } }) } } func TestDefaultParams(t *testing.T) { - // Test that default params are valid defaultParams := types.DefaultParams() require.NoError(t, defaultParams.ValidateBasic()) - - // Verify specific default values require.Equal(t, "akii", defaultParams.TokenDenom) + require.True(t, defaultParams.SupplyBase.IsZero()) + require.True(t, math.LegacyNewDecWithPrec(67, 2).Equal(defaultParams.GoalBonded)) + require.True(t, math.LegacyNewDecWithPrec(20, 2).Equal(defaultParams.InflationMax)) + require.True(t, defaultParams.InflationMin.IsZero()) + require.True(t, math.LegacyNewDecWithPrec(13, 2).Equal(defaultParams.InflationRateChange)) } diff --git a/x/rewards/types/query.pb.go b/x/rewards/types/query.pb.go index 935d72a7..b79d657b 100644 --- a/x/rewards/types/query.pb.go +++ b/x/rewards/types/query.pb.go @@ -6,7 +6,6 @@ package types import ( context "context" fmt "fmt" - _ "github.com/cosmos/cosmos-sdk/types/query" _ "github.com/cosmos/gogoproto/gogoproto" grpc1 "github.com/cosmos/gogoproto/grpc" proto "github.com/cosmos/gogoproto/proto" @@ -113,90 +112,6 @@ func (m *QueryParamsResponse) GetParams() Params { return Params{} } -// QueryReleaseScheduleRequest defines the request structure for the -// ReleaseSchedule gRPC query. -type QueryReleaseScheduleRequest struct { -} - -func (m *QueryReleaseScheduleRequest) Reset() { *m = QueryReleaseScheduleRequest{} } -func (m *QueryReleaseScheduleRequest) String() string { return proto.CompactTextString(m) } -func (*QueryReleaseScheduleRequest) ProtoMessage() {} -func (*QueryReleaseScheduleRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_12435df56ac62847, []int{2} -} -func (m *QueryReleaseScheduleRequest) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *QueryReleaseScheduleRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_QueryReleaseScheduleRequest.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *QueryReleaseScheduleRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryReleaseScheduleRequest.Merge(m, src) -} -func (m *QueryReleaseScheduleRequest) XXX_Size() int { - return m.Size() -} -func (m *QueryReleaseScheduleRequest) XXX_DiscardUnknown() { - xxx_messageInfo_QueryReleaseScheduleRequest.DiscardUnknown(m) -} - -var xxx_messageInfo_QueryReleaseScheduleRequest proto.InternalMessageInfo - -// QueryReleaseScheduleResponse defines the response structure for the -// ReleaseSchedule gRPC query. -type QueryReleaseScheduleResponse struct { - ReleaseSchedule ReleaseSchedule `protobuf:"bytes,1,opt,name=release_schedule,json=releaseSchedule,proto3" json:"release_schedule" yaml:"release_schedule"` -} - -func (m *QueryReleaseScheduleResponse) Reset() { *m = QueryReleaseScheduleResponse{} } -func (m *QueryReleaseScheduleResponse) String() string { return proto.CompactTextString(m) } -func (*QueryReleaseScheduleResponse) ProtoMessage() {} -func (*QueryReleaseScheduleResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_12435df56ac62847, []int{3} -} -func (m *QueryReleaseScheduleResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *QueryReleaseScheduleResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_QueryReleaseScheduleResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *QueryReleaseScheduleResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryReleaseScheduleResponse.Merge(m, src) -} -func (m *QueryReleaseScheduleResponse) XXX_Size() int { - return m.Size() -} -func (m *QueryReleaseScheduleResponse) XXX_DiscardUnknown() { - xxx_messageInfo_QueryReleaseScheduleResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_QueryReleaseScheduleResponse proto.InternalMessageInfo - -func (m *QueryReleaseScheduleResponse) GetReleaseSchedule() ReleaseSchedule { - if m != nil { - return m.ReleaseSchedule - } - return ReleaseSchedule{} -} - // QueryRewardPoolRequest defines the request structure for the // RewardPool gRPC query. type QueryRewardPoolRequest struct { @@ -206,7 +121,7 @@ func (m *QueryRewardPoolRequest) Reset() { *m = QueryRewardPoolRequest{} func (m *QueryRewardPoolRequest) String() string { return proto.CompactTextString(m) } func (*QueryRewardPoolRequest) ProtoMessage() {} func (*QueryRewardPoolRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_12435df56ac62847, []int{4} + return fileDescriptor_12435df56ac62847, []int{2} } func (m *QueryRewardPoolRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -245,7 +160,7 @@ func (m *QueryRewardPoolResponse) Reset() { *m = QueryRewardPoolResponse func (m *QueryRewardPoolResponse) String() string { return proto.CompactTextString(m) } func (*QueryRewardPoolResponse) ProtoMessage() {} func (*QueryRewardPoolResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_12435df56ac62847, []int{5} + return fileDescriptor_12435df56ac62847, []int{3} } func (m *QueryRewardPoolResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -284,8 +199,6 @@ func (m *QueryRewardPoolResponse) GetRewardPool() RewardPool { func init() { proto.RegisterType((*QueryParamsRequest)(nil), "kiichain.rewards.v1beta1.QueryParamsRequest") proto.RegisterType((*QueryParamsResponse)(nil), "kiichain.rewards.v1beta1.QueryParamsResponse") - proto.RegisterType((*QueryReleaseScheduleRequest)(nil), "kiichain.rewards.v1beta1.QueryReleaseScheduleRequest") - proto.RegisterType((*QueryReleaseScheduleResponse)(nil), "kiichain.rewards.v1beta1.QueryReleaseScheduleResponse") proto.RegisterType((*QueryRewardPoolRequest)(nil), "kiichain.rewards.v1beta1.QueryRewardPoolRequest") proto.RegisterType((*QueryRewardPoolResponse)(nil), "kiichain.rewards.v1beta1.QueryRewardPoolResponse") } @@ -295,39 +208,31 @@ func init() { } var fileDescriptor_12435df56ac62847 = []byte{ - // 500 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x94, 0xcf, 0x6e, 0xd3, 0x40, - 0x10, 0xc6, 0xb3, 0xfc, 0xc9, 0x61, 0x7b, 0x28, 0x5a, 0x2a, 0x1a, 0x99, 0xe2, 0x44, 0xab, 0x56, - 0x94, 0x2a, 0xb1, 0x49, 0x10, 0x1c, 0x38, 0x70, 0xc8, 0x81, 0x73, 0x31, 0xe2, 0xc2, 0xa5, 0xda, - 0xa4, 0x2b, 0xc7, 0xc2, 0xf1, 0xb8, 0x5e, 0x1b, 0x88, 0xc4, 0x89, 0x17, 0x00, 0x09, 0x71, 0xe7, - 0x21, 0x78, 0x88, 0x1e, 0x2b, 0x71, 0xe1, 0x54, 0xa1, 0x84, 0x27, 0x80, 0x17, 0x40, 0xde, 0x1d, - 0xbb, 0xaa, 0x5b, 0x37, 0xe4, 0xe6, 0xec, 0x7c, 0xf3, 0x7d, 0xbf, 0xec, 0x8c, 0x96, 0x6e, 0xbf, - 0x09, 0x82, 0xf1, 0x44, 0x04, 0x91, 0x9b, 0xc8, 0x77, 0x22, 0x39, 0x54, 0xee, 0xdb, 0xfe, 0x48, - 0xa6, 0xa2, 0xef, 0x1e, 0x65, 0x32, 0x99, 0x39, 0x71, 0x02, 0x29, 0xb0, 0x56, 0xa1, 0x72, 0x50, - 0xe5, 0xa0, 0xca, 0xda, 0xf0, 0xc1, 0x07, 0x2d, 0x72, 0xf3, 0x2f, 0xa3, 0xb7, 0xb6, 0x7c, 0x00, - 0x3f, 0x94, 0xae, 0x88, 0x03, 0x57, 0x44, 0x11, 0xa4, 0x22, 0x0d, 0x20, 0x52, 0x58, 0xdd, 0x1b, - 0x83, 0x9a, 0x82, 0x72, 0x47, 0x42, 0x49, 0x13, 0x53, 0x86, 0xc6, 0xc2, 0x0f, 0x22, 0x2d, 0x46, - 0x6d, 0x3d, 0x5f, 0x3a, 0x8b, 0x65, 0xe1, 0xb8, 0x53, 0xab, 0x8a, 0x45, 0x22, 0xa6, 0x28, 0xe3, - 0x1b, 0x94, 0xbd, 0xc8, 0xe3, 0xf6, 0xf5, 0xa1, 0x27, 0x8f, 0x32, 0xa9, 0x52, 0xfe, 0x8a, 0xde, - 0x3e, 0x77, 0xaa, 0x62, 0x88, 0x94, 0x64, 0xcf, 0x68, 0xd3, 0x34, 0xb7, 0x48, 0x87, 0xec, 0xae, - 0x0d, 0x3a, 0x4e, 0xdd, 0x25, 0x38, 0xa6, 0x73, 0x78, 0xe3, 0xf8, 0xb4, 0xdd, 0xf0, 0xb0, 0x8b, - 0xdf, 0xa3, 0x77, 0xb5, 0xad, 0x27, 0x43, 0x29, 0x94, 0x7c, 0x39, 0x9e, 0xc8, 0xc3, 0x2c, 0x94, - 0x45, 0xea, 0x57, 0x42, 0xb7, 0x2e, 0xaf, 0x63, 0x7e, 0x46, 0x6f, 0x25, 0xa6, 0x74, 0xa0, 0xb0, - 0x86, 0x24, 0x0f, 0xea, 0x49, 0x2a, 0x66, 0xc3, 0x76, 0x8e, 0xf4, 0xe7, 0xb4, 0xbd, 0x39, 0x13, - 0xd3, 0xf0, 0x29, 0xaf, 0x1a, 0x72, 0x6f, 0x3d, 0x39, 0xdf, 0xc1, 0x5b, 0xf4, 0x0e, 0x62, 0xe5, - 0xce, 0xfb, 0x00, 0x61, 0x41, 0xfc, 0x81, 0x6e, 0x5e, 0xa8, 0x20, 0xab, 0xa0, 0x6b, 0x86, 0xe4, - 0x20, 0x06, 0x08, 0x11, 0x73, 0xfb, 0x2a, 0xcc, 0xc2, 0x62, 0x68, 0x21, 0x21, 0x2b, 0x08, 0x4b, - 0x1b, 0xee, 0xd1, 0xa4, 0xd4, 0x0d, 0xfe, 0x5e, 0xa7, 0x37, 0x75, 0x3c, 0xfb, 0x44, 0x68, 0xd3, - 0xdc, 0x38, 0xeb, 0xd6, 0x47, 0x5c, 0x1c, 0xb4, 0xd5, 0xfb, 0x4f, 0xb5, 0xf9, 0x53, 0x7c, 0xf7, - 0xe3, 0x8f, 0xdf, 0x5f, 0xae, 0x71, 0xd6, 0x71, 0x97, 0x6c, 0x17, 0xfb, 0x4e, 0xe8, 0x7a, 0xe5, - 0xe6, 0xd9, 0xe3, 0x25, 0x61, 0x97, 0xaf, 0x85, 0xf5, 0x64, 0xd5, 0x36, 0x84, 0x1d, 0x68, 0xd8, - 0x2e, 0xdb, 0xab, 0x87, 0xc5, 0x49, 0xf7, 0x8a, 0xe1, 0xb3, 0x6f, 0x84, 0xd2, 0xb3, 0x49, 0xb0, - 0x87, 0x4b, 0xa3, 0x2b, 0x1b, 0x61, 0xf5, 0x57, 0xe8, 0x40, 0xce, 0x9e, 0xe6, 0xbc, 0xcf, 0x76, - 0xae, 0xe2, 0xcc, 0x7f, 0xf7, 0xf2, 0x15, 0x18, 0x3e, 0x3f, 0x9e, 0xdb, 0xe4, 0x64, 0x6e, 0x93, - 0x5f, 0x73, 0x9b, 0x7c, 0x5e, 0xd8, 0x8d, 0x93, 0x85, 0xdd, 0xf8, 0xb9, 0xb0, 0x1b, 0xaf, 0xbb, - 0x7e, 0x90, 0x4e, 0xb2, 0x91, 0x33, 0x86, 0xe9, 0x99, 0x55, 0xf9, 0xf1, 0xbe, 0x74, 0xd5, 0xcf, - 0xc4, 0xa8, 0xa9, 0x1f, 0x80, 0x47, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0xbe, 0x94, 0x24, 0x7b, - 0xef, 0x04, 0x00, 0x00, + // 382 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xc9, 0xce, 0xcc, 0x4c, + 0xce, 0x48, 0xcc, 0xcc, 0xd3, 0x2f, 0x4a, 0x2d, 0x4f, 0x2c, 0x4a, 0x29, 0xd6, 0x2f, 0x33, 0x4c, + 0x4a, 0x2d, 0x49, 0x34, 0xd4, 0x2f, 0x2c, 0x4d, 0x2d, 0xaa, 0xd4, 0x2b, 0x28, 0xca, 0x2f, 0xc9, + 0x17, 0x92, 0x80, 0xa9, 0xd2, 0x83, 0xaa, 0xd2, 0x83, 0xaa, 0x92, 0x12, 0x49, 0xcf, 0x4f, 0xcf, + 0x07, 0x2b, 0xd2, 0x07, 0xb1, 0x20, 0xea, 0xa5, 0x64, 0xd2, 0xf3, 0xf3, 0xd3, 0x73, 0x52, 0xf5, + 0x13, 0x0b, 0x32, 0xf5, 0x13, 0xf3, 0xf2, 0xf2, 0x4b, 0x12, 0x4b, 0x32, 0xf3, 0xf3, 0x8a, 0xa1, + 0xb2, 0xb8, 0xed, 0x2c, 0xa9, 0x2c, 0x48, 0x85, 0xa9, 0x52, 0xc5, 0xa9, 0xaa, 0x20, 0xb1, 0x28, + 0x31, 0x17, 0xaa, 0x4c, 0x49, 0x84, 0x4b, 0x28, 0x10, 0xe4, 0xd2, 0x00, 0xb0, 0x60, 0x50, 0x6a, + 0x61, 0x69, 0x6a, 0x71, 0x89, 0x52, 0x28, 0x97, 0x30, 0x8a, 0x68, 0x71, 0x41, 0x7e, 0x5e, 0x71, + 0xaa, 0x90, 0x1d, 0x17, 0x1b, 0x44, 0xb3, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0xb7, 0x91, 0x82, 0x1e, + 0x2e, 0x8f, 0xe9, 0x41, 0x74, 0x3a, 0xb1, 0x9c, 0xb8, 0x27, 0xcf, 0x10, 0x04, 0xd5, 0xa5, 0x24, + 0xc1, 0x25, 0x06, 0x36, 0x36, 0x08, 0xac, 0x38, 0x20, 0x3f, 0x3f, 0x07, 0x66, 0x61, 0x0d, 0x97, + 0x38, 0x86, 0x0c, 0xd4, 0xd2, 0x44, 0x2e, 0x6e, 0x88, 0xe1, 0xf1, 0x05, 0xf9, 0xf9, 0x39, 0x50, + 0x9b, 0x55, 0x70, 0xdb, 0x8c, 0x30, 0xc2, 0x49, 0x0a, 0x64, 0xfb, 0xa7, 0x7b, 0xf2, 0x42, 0x95, + 0x89, 0xb9, 0x39, 0x56, 0x4a, 0x48, 0xc6, 0x28, 0x05, 0x71, 0x15, 0xc1, 0xd5, 0x19, 0xed, 0x61, + 0xe2, 0x62, 0x05, 0x5b, 0x2f, 0xd4, 0xcf, 0xc8, 0xc5, 0x06, 0x71, 0xba, 0x90, 0x0e, 0x6e, 0x2b, + 0x30, 0x43, 0x4c, 0x4a, 0x97, 0x48, 0xd5, 0x10, 0x4f, 0x29, 0x69, 0x34, 0x5d, 0x7e, 0x32, 0x99, + 0x49, 0x49, 0x48, 0x41, 0x9f, 0x40, 0x34, 0x09, 0x2d, 0x60, 0xe4, 0xe2, 0x42, 0x78, 0x49, 0xc8, + 0x80, 0x80, 0x3d, 0x18, 0x41, 0x2b, 0x65, 0x48, 0x82, 0x0e, 0xa8, 0xeb, 0x74, 0xc1, 0xae, 0x53, + 0x17, 0x52, 0xc5, 0xed, 0x3a, 0x08, 0x5f, 0x17, 0x14, 0x96, 0x4e, 0x6e, 0x27, 0x1e, 0xc9, 0x31, + 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, + 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0xa5, 0x93, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, + 0x9f, 0x8b, 0x30, 0x0a, 0xce, 0xa8, 0x80, 0x9b, 0x0a, 0x4e, 0xb8, 0x49, 0x6c, 0xe0, 0x24, 0x69, + 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x18, 0xc3, 0x92, 0x28, 0x55, 0x03, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -345,9 +250,6 @@ type QueryClient interface { // Params defines a gRPC query method that returns the reward module's // parameters. Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) - // ReleaseSchedule defines a gRPC query method for fetching - // ReleaseSchedule data. - ReleaseSchedule(ctx context.Context, in *QueryReleaseScheduleRequest, opts ...grpc.CallOption) (*QueryReleaseScheduleResponse, error) // RewardPool defines a gRPC query method for fetching // RewardPool data. RewardPool(ctx context.Context, in *QueryRewardPoolRequest, opts ...grpc.CallOption) (*QueryRewardPoolResponse, error) @@ -370,15 +272,6 @@ func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts . return out, nil } -func (c *queryClient) ReleaseSchedule(ctx context.Context, in *QueryReleaseScheduleRequest, opts ...grpc.CallOption) (*QueryReleaseScheduleResponse, error) { - out := new(QueryReleaseScheduleResponse) - err := c.cc.Invoke(ctx, "/kiichain.rewards.v1beta1.Query/ReleaseSchedule", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *queryClient) RewardPool(ctx context.Context, in *QueryRewardPoolRequest, opts ...grpc.CallOption) (*QueryRewardPoolResponse, error) { out := new(QueryRewardPoolResponse) err := c.cc.Invoke(ctx, "/kiichain.rewards.v1beta1.Query/RewardPool", in, out, opts...) @@ -393,9 +286,6 @@ type QueryServer interface { // Params defines a gRPC query method that returns the reward module's // parameters. Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) - // ReleaseSchedule defines a gRPC query method for fetching - // ReleaseSchedule data. - ReleaseSchedule(context.Context, *QueryReleaseScheduleRequest) (*QueryReleaseScheduleResponse, error) // RewardPool defines a gRPC query method for fetching // RewardPool data. RewardPool(context.Context, *QueryRewardPoolRequest) (*QueryRewardPoolResponse, error) @@ -408,9 +298,6 @@ type UnimplementedQueryServer struct { func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") } -func (*UnimplementedQueryServer) ReleaseSchedule(ctx context.Context, req *QueryReleaseScheduleRequest) (*QueryReleaseScheduleResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method ReleaseSchedule not implemented") -} func (*UnimplementedQueryServer) RewardPool(ctx context.Context, req *QueryRewardPoolRequest) (*QueryRewardPoolResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RewardPool not implemented") } @@ -437,24 +324,6 @@ func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interf return interceptor(ctx, in, info, handler) } -func _Query_ReleaseSchedule_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(QueryReleaseScheduleRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(QueryServer).ReleaseSchedule(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/kiichain.rewards.v1beta1.Query/ReleaseSchedule", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServer).ReleaseSchedule(ctx, req.(*QueryReleaseScheduleRequest)) - } - return interceptor(ctx, in, info, handler) -} - func _Query_RewardPool_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(QueryRewardPoolRequest) if err := dec(in); err != nil { @@ -473,6 +342,7 @@ func _Query_RewardPool_Handler(srv interface{}, ctx context.Context, dec func(in return interceptor(ctx, in, info, handler) } +var Query_serviceDesc = _Query_serviceDesc var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "kiichain.rewards.v1beta1.Query", HandlerType: (*QueryServer)(nil), @@ -481,10 +351,6 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "Params", Handler: _Query_Params_Handler, }, - { - MethodName: "ReleaseSchedule", - Handler: _Query_ReleaseSchedule_Handler, - }, { MethodName: "RewardPool", Handler: _Query_RewardPool_Handler, @@ -550,62 +416,6 @@ func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *QueryReleaseScheduleRequest) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *QueryReleaseScheduleRequest) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *QueryReleaseScheduleRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - return len(dAtA) - i, nil -} - -func (m *QueryReleaseScheduleResponse) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *QueryReleaseScheduleResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *QueryReleaseScheduleResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size, err := m.ReleaseSchedule.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintQuery(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - func (m *QueryRewardPoolRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -693,26 +503,6 @@ func (m *QueryParamsResponse) Size() (n int) { return n } -func (m *QueryReleaseScheduleRequest) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - return n -} - -func (m *QueryReleaseScheduleResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.ReleaseSchedule.Size() - n += 1 + l + sovQuery(uint64(l)) - return n -} - func (m *QueryRewardPoolRequest) Size() (n int) { if m == nil { return 0 @@ -872,139 +662,6 @@ func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *QueryReleaseScheduleRequest) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: QueryReleaseScheduleRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: QueryReleaseScheduleRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipQuery(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthQuery - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *QueryReleaseScheduleResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: QueryReleaseScheduleResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: QueryReleaseScheduleResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ReleaseSchedule", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthQuery - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthQuery - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.ReleaseSchedule.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipQuery(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthQuery - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *QueryRewardPoolRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/rewards/types/query.pb.gw.go b/x/rewards/types/query.pb.gw.go index 07d7f524..0ee01381 100644 --- a/x/rewards/types/query.pb.gw.go +++ b/x/rewards/types/query.pb.gw.go @@ -51,24 +51,6 @@ func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshal } -func request_Query_ReleaseSchedule_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryReleaseScheduleRequest - var metadata runtime.ServerMetadata - - msg, err := client.ReleaseSchedule(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) - return msg, metadata, err - -} - -func local_request_Query_ReleaseSchedule_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryReleaseScheduleRequest - var metadata runtime.ServerMetadata - - msg, err := server.ReleaseSchedule(ctx, &protoReq) - return msg, metadata, err - -} - func request_Query_RewardPool_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq QueryRewardPoolRequest var metadata runtime.ServerMetadata @@ -116,29 +98,6 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) - mux.Handle("GET", pattern_Query_ReleaseSchedule_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - var stream runtime.ServerTransportStream - ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_Query_ReleaseSchedule_0(rctx, inboundMarshaler, server, req, pathParams) - md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_Query_ReleaseSchedule_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - mux.Handle("GET", pattern_Query_RewardPool_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -223,26 +182,6 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) - mux.Handle("GET", pattern_Query_ReleaseSchedule_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := request_Query_ReleaseSchedule_0(rctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_Query_ReleaseSchedule_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - mux.Handle("GET", pattern_Query_RewardPool_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -269,15 +208,11 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie var ( pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"kiichain", "rewards", "v1beta1", "params"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_ReleaseSchedule_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"kiichain", "rewards", "v1beta1", "release-schedule"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_RewardPool_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"kiichain", "rewards", "v1beta1", "reward-pool"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( forward_Query_Params_0 = runtime.ForwardResponseMessage - forward_Query_ReleaseSchedule_0 = runtime.ForwardResponseMessage - forward_Query_RewardPool_0 = runtime.ForwardResponseMessage ) diff --git a/x/rewards/types/release_schedule.go b/x/rewards/types/release_schedule.go deleted file mode 100644 index 5bc9bfc1..00000000 --- a/x/rewards/types/release_schedule.go +++ /dev/null @@ -1,54 +0,0 @@ -package types - -import ( - fmt "fmt" - time "time" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// InitialReleaseSchedule returns an empty release schedule -func InitialReleaseSchedule() ReleaseSchedule { - return ReleaseSchedule{ - TotalAmount: sdk.Coin{}, - ReleasedAmount: sdk.Coin{}, - EndTime: time.Time{}, - LastReleaseTime: time.Time{}, - Active: false, - } -} - -// ValidateGenesis validates the release schedule for a genesis state -func (rr ReleaseSchedule) ValidateGenesis() error { - // Some validations just make sense if active - if rr.Active { - // Validate TotalAmount - if err := rr.TotalAmount.Validate(); err != nil { - return fmt.Errorf("invalid total amount: %w", err) - } - if rr.TotalAmount.IsZero() { - return fmt.Errorf("active reward releaser cannot have zero total amount") - } - if rr.EndTime.IsZero() { - return fmt.Errorf("active reward releaser must have an end time") - } - // Validate ReleasedAmount if not zero - if !rr.ReleasedAmount.IsZero() { - if err := rr.ReleasedAmount.Validate(); err != nil { - return fmt.Errorf("invalid released amount: %w", err) - } - - // Check ReleasedAmount doesn't exceed TotalAmount - if rr.ReleasedAmount.Denom != rr.TotalAmount.Denom { - return fmt.Errorf("released amount denom %s doesn't match total amount denom %s", - rr.ReleasedAmount.Denom, rr.TotalAmount.Denom) - } - - if rr.ReleasedAmount.Amount.GT(rr.TotalAmount.Amount) { - return fmt.Errorf("released amount %s cannot be greater than total amount %s", - rr.ReleasedAmount.String(), rr.TotalAmount.String()) - } - } - } - return nil -} diff --git a/x/rewards/types/release_schedule_test.go b/x/rewards/types/release_schedule_test.go deleted file mode 100644 index 4034520d..00000000 --- a/x/rewards/types/release_schedule_test.go +++ /dev/null @@ -1,130 +0,0 @@ -package types_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/require" - - "cosmossdk.io/math" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/kiichain/kiichain/v7/x/rewards/types" -) - -func TestRewardReleaserValidateGenesis(t *testing.T) { - now := time.Now() - validCoin := sdk.NewCoin("akii", math.NewInt(1000)) - invalidCoin := sdk.Coin{Denom: "invalid!", Amount: math.NewInt(-1)} - - tests := []struct { - name string - schedule types.ReleaseSchedule - wantErr bool - errMsg string - }{ - { - name: "valid initial state", - schedule: types.InitialReleaseSchedule(), - wantErr: false, - }, - { - name: "valid active release", - schedule: types.ReleaseSchedule{ - TotalAmount: validCoin, - ReleasedAmount: sdk.NewCoin("akii", math.NewInt(500)), - EndTime: now.Add(time.Hour * 24), - LastReleaseTime: now, - Active: true, - }, - wantErr: false, - }, - { - name: "invalid total amount", - schedule: types.ReleaseSchedule{ - TotalAmount: invalidCoin, - ReleasedAmount: sdk.Coin{}, - EndTime: time.Time{}, - LastReleaseTime: time.Time{}, - Active: true, - }, - wantErr: true, - errMsg: "invalid total amount", - }, - { - name: "invalid released amount", - schedule: types.ReleaseSchedule{ - TotalAmount: validCoin, - ReleasedAmount: invalidCoin, - EndTime: now.Add(time.Hour * 24), - LastReleaseTime: now, - Active: true, - }, - wantErr: true, - errMsg: "invalid released amount", - }, - { - name: "denom mismatch", - schedule: types.ReleaseSchedule{ - TotalAmount: validCoin, - ReleasedAmount: sdk.NewCoin("otherdenom", math.NewInt(500)), - EndTime: now.Add(time.Hour * 24), - LastReleaseTime: now, - Active: true, - }, - wantErr: true, - errMsg: "doesn't match total amount denom", - }, - { - name: "released exceeds total", - schedule: types.ReleaseSchedule{ - TotalAmount: validCoin, - ReleasedAmount: sdk.NewCoin("akii", math.NewInt(2000)), - EndTime: now.Add(time.Hour * 24), - LastReleaseTime: now, - Active: true, - }, - wantErr: true, - errMsg: "cannot be greater than total amount", - }, - { - name: "active with zero total", - schedule: types.ReleaseSchedule{ - TotalAmount: sdk.Coin{Denom: "akii", Amount: math.NewInt(0)}, - ReleasedAmount: sdk.Coin{}, - EndTime: now.Add(time.Hour * 24), - LastReleaseTime: time.Time{}, - Active: true, - }, - wantErr: true, - errMsg: "cannot have zero total amount", - }, - { - name: "active with zero end time", - schedule: types.ReleaseSchedule{ - TotalAmount: validCoin, - ReleasedAmount: sdk.Coin{}, - EndTime: time.Time{}, - LastReleaseTime: time.Time{}, - Active: true, - }, - wantErr: true, - errMsg: "must have an end time", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.schedule.ValidateGenesis() - if tt.wantErr { - require.Error(t, err) - if tt.errMsg != "" { - require.Contains(t, err.Error(), tt.errMsg) - } - } else { - require.NoError(t, err) - } - }) - } -} diff --git a/x/rewards/types/reward.go b/x/rewards/types/reward.go index 91abafcc..b40e9367 100644 --- a/x/rewards/types/reward.go +++ b/x/rewards/types/reward.go @@ -8,36 +8,60 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// CalculateReward figures what amt to be released in the current block -// Assumes invalid values are cleared before calling, does not handle invalid blockTime/no last release -func CalculateReward(blockTime time.Time, schedule ReleaseSchedule) (sdk.Coin, error) { - // Calculate remaining amount - remaining := schedule.TotalAmount.Sub(schedule.ReleasedAmount) - if remaining.IsZero() { - return remaining, nil - } +// SecondsPerYear is the reference year length (365 days) used to convert an +// annual emission rate into a per-block provision. +const SecondsPerYear = 31_536_000 + +// CalculateInflation returns the KiiChain emission rate for a bonded ratio. +// The bonded-ratio multiplier forms a distribution (bell) curve, then clamps +// to [InflationMin, InflationMax]. +// +// inflation = (1 - bondedRatio/goalBonded) * inflationRateChange * bondedRatio +func CalculateInflation(bondedRatio math.LegacyDec, p Params) math.LegacyDec { + inflation := math.LegacyOneDec(). + Sub(bondedRatio.Quo(p.GoalBonded)). + Mul(p.InflationRateChange). + Mul(bondedRatio) - // If total duration is zero or negative, release the full remaining amount - totalDurationNs := schedule.EndTime.Sub(schedule.LastReleaseTime).Nanoseconds() - if totalDurationNs <= 0 { - return remaining, nil + if inflation.LT(p.InflationMin) { + return p.InflationMin } + if inflation.GT(p.InflationMax) { + return p.InflationMax + } + return inflation +} - // Get time parameters - timeElapsedStamp := blockTime.Sub(schedule.LastReleaseTime) // Time since last release - totalDurationStamp := schedule.EndTime.Sub(schedule.LastReleaseTime) // Remaining release period +// CalculateReward returns the amount to release this block and the inflation +// rate used. The annual provision (inflation * supplyBase) is prorated by the +// wall-clock time since the last release. No tokens are minted; funds come +// from the prefunded pool. +func CalculateReward( + blockTime time.Time, + lastReleaseTime time.Time, + bondedRatio math.LegacyDec, + p Params, +) (sdk.Coin, math.LegacyDec) { + denom := p.TokenDenom + zero := sdk.NewCoin(denom, math.ZeroInt()) - // Use nanoseconds for precise duration calculations - timeElapsedNs := math.NewInt(timeElapsedStamp.Nanoseconds()).BigInt() - totalDurationNsBig := math.NewInt(totalDurationStamp.Nanoseconds()).BigInt() + if p.SupplyBase.IsZero() { + return zero, math.LegacyZeroDec() + } + + inflation := CalculateInflation(bondedRatio, p) + annualProvision := inflation.MulInt(p.SupplyBase) - // Calculate linear release proportion between 0 and 1 - releaseProportion := math.LegacyNewDecFromBigInt(timeElapsedNs).Quo(math.LegacyNewDecFromBigInt(totalDurationNsBig)) - // Truncate to int, it will be a coin amt after all - amountToRelease := math.LegacyNewDecFromInt(remaining.Amount).Mul(releaseProportion).TruncateInt() + elapsedNs := blockTime.Sub(lastReleaseTime).Nanoseconds() + if elapsedNs <= 0 { + return zero, inflation + } - // Cap at remaining amount - amountToRelease = math.MinInt(amountToRelease, remaining.Amount) + const nsPerYear = int64(SecondsPerYear) * 1_000_000_000 + amount := annualProvision. + MulInt64(elapsedNs). + QuoInt64(nsPerYear). + TruncateInt() - return sdk.NewCoin(schedule.TotalAmount.Denom, amountToRelease), nil + return sdk.NewCoin(denom, amount), inflation } diff --git a/x/rewards/types/reward_pool.go b/x/rewards/types/reward_pool.go index b57e67c9..42fde133 100644 --- a/x/rewards/types/reward_pool.go +++ b/x/rewards/types/reward_pool.go @@ -2,6 +2,7 @@ package types import ( "fmt" + "time" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -9,7 +10,9 @@ import ( // InitialRewardPool returns a zero reward pool func InitialRewardPool() RewardPool { return RewardPool{ - CommunityPool: sdk.DecCoins{}, + CommunityPool: sdk.DecCoins{}, + LastReleaseTime: time.Time{}, + TotalReleased: sdk.Coin{}, } } @@ -19,5 +22,11 @@ func (rp RewardPool) ValidateGenesis() error { return fmt.Errorf("invalid CommunityPool: %w", err) } + if !rp.TotalReleased.IsNil() && !rp.TotalReleased.IsZero() { + if err := rp.TotalReleased.Validate(); err != nil { + return fmt.Errorf("invalid TotalReleased: %w", err) + } + } + return nil } diff --git a/x/rewards/types/reward_pool_test.go b/x/rewards/types/reward_pool_test.go index ad08d754..80c4d2d4 100644 --- a/x/rewards/types/reward_pool_test.go +++ b/x/rewards/types/reward_pool_test.go @@ -70,6 +70,14 @@ func TestRewardPoolValidateGenesis(t *testing.T) { }, expectErr: true, }, + { + name: "invalid total released coin", + pool: types.RewardPool{ + CommunityPool: sdk.DecCoins{}, + TotalReleased: sdk.Coin{Denom: "1bad", Amount: math.NewInt(1)}, + }, + expectErr: true, + }, } for _, tc := range testCases { diff --git a/x/rewards/types/reward_test.go b/x/rewards/types/reward_test.go index 888c7662..ec799979 100644 --- a/x/rewards/types/reward_test.go +++ b/x/rewards/types/reward_test.go @@ -8,167 +8,193 @@ import ( "cosmossdk.io/math" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/kiichain/kiichain/v7/x/rewards/types" ) -// TestCalculateReward verifies the CalculateReward function across a comprehensive -// set of scenarios: fully-exhausted schedules, linear proportional releases at -// various elapsed fractions, partial prior releases, nanosecond-precision -// sub-second durations (regression for issue #267), and the edge case where -// EndTime equals LastReleaseTime, which must release the full remaining amount -// immediately rather than returning an error. -func TestCalculateReward(t *testing.T) { - now := time.Now() - denom := "akii" +func TestCalculateInflation(t *testing.T) { + params := types.DefaultParams() tests := []struct { - name string - blockTime time.Time - schedule types.ReleaseSchedule - expectedCoin sdk.Coin - expectedError bool + name string + bondedRatio math.LegacyDec + want math.LegacyDec }{ { - name: "nothing left to release", - blockTime: now.Add(time.Hour), - schedule: types.ReleaseSchedule{ - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000)), - ReleasedAmount: sdk.NewCoin(denom, math.NewInt(1000)), - LastReleaseTime: now, - EndTime: now.Add(time.Hour * 2), - Active: true, - }, - expectedCoin: sdk.NewCoin(denom, math.ZeroInt()), - expectedError: false, - }, - { - name: "linear release - halfway", - blockTime: now.Add(time.Hour), - schedule: types.ReleaseSchedule{ - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000)), - ReleasedAmount: sdk.NewCoin(denom, math.ZeroInt()), - LastReleaseTime: now, - EndTime: now.Add(time.Hour * 2), - Active: true, - }, - expectedCoin: sdk.NewCoin(denom, math.NewInt(500)), - expectedError: false, - }, - { - name: "linear release - one third", - blockTime: now.Add(20 * time.Minute), - schedule: types.ReleaseSchedule{ - TotalAmount: sdk.NewCoin(denom, math.NewInt(900)), - ReleasedAmount: sdk.NewCoin(denom, math.ZeroInt()), - LastReleaseTime: now, - EndTime: now.Add(time.Hour), - Active: true, - }, - expectedCoin: sdk.NewCoin(denom, math.NewInt(300)), - expectedError: false, - }, - { - name: "partial release with existing released amount", - blockTime: now.Add(30 * time.Minute), - schedule: types.ReleaseSchedule{ - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000)), - ReleasedAmount: sdk.NewCoin(denom, math.NewInt(200)), - LastReleaseTime: now, - EndTime: now.Add(time.Hour), - Active: true, - }, - expectedCoin: sdk.NewCoin(denom, math.NewInt(400)), // (1000-200) * (30/60) - expectedError: false, + name: "zero bonded ratio -> inflation min", + bondedRatio: math.LegacyZeroDec(), + want: params.InflationMin, }, { - name: "normal case but with small fraction", - blockTime: now.Add(3 * time.Second), - schedule: types.ReleaseSchedule{ - TotalAmount: sdk.NewCoin(denom, math.NewInt(3000000000000000000)), // 3 kii - ReleasedAmount: sdk.NewCoin(denom, math.NewInt(2000000000000000000)), - LastReleaseTime: now, - EndTime: now.Add(time.Hour * 24 * 365), // one year - Active: true, - }, - // 1 kii / 10512000 (365*24*60*20) ≃ 1*10^11 - expectedCoin: sdk.NewCoin(denom, math.NewInt(95129375951)), - expectedError: false, + name: "at goal bonded -> zero before clamp, returns min", + bondedRatio: params.GoalBonded, + want: params.InflationMin, }, { - name: "last release (past end time)", - blockTime: now.Add(time.Hour * 2), - schedule: types.ReleaseSchedule{ - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000)), - ReleasedAmount: sdk.NewCoin(denom, math.NewInt(800)), - LastReleaseTime: now, - EndTime: now.Add(time.Hour), - Active: true, - }, - expectedCoin: sdk.NewCoin(denom, math.NewInt(200)), // Cap at remaining 200 - expectedError: false, + name: "above goal bonded -> negative raw, clamped to min", + bondedRatio: math.LegacyNewDecWithPrec(80, 2), + want: params.InflationMin, }, { - name: "end time equal to last release releases full remaining amount", - blockTime: now.Add(time.Hour * 2), - schedule: types.ReleaseSchedule{ - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000)), - ReleasedAmount: sdk.NewCoin(denom, math.NewInt(800)), - LastReleaseTime: now, - EndTime: now, - Active: true, - }, - expectedCoin: sdk.NewCoin(denom, math.NewInt(200)), // full remaining released - expectedError: false, + name: "peak at goalBonded/2", + bondedRatio: params.GoalBonded.QuoInt64(2), + // peak = rateChange * goalBonded / 4 = 0.13 * 0.67 / 4 + want: params.InflationRateChange.Mul(params.GoalBonded).QuoInt64(4), }, { - name: "sub-second duration does not divide by zero", - blockTime: now.Add(250 * time.Millisecond), - schedule: types.ReleaseSchedule{ - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000)), - ReleasedAmount: sdk.NewCoin(denom, math.ZeroInt()), - LastReleaseTime: now, - EndTime: now.Add(500 * time.Millisecond), - Active: true, - }, - expectedCoin: sdk.NewCoin(denom, math.NewInt(500)), // 250ms / 500ms = 50% - expectedError: false, + name: "mid curve below peak", + bondedRatio: math.LegacyNewDecWithPrec(20, 2), + want: math.LegacyOneDec(). + Sub(math.LegacyNewDecWithPrec(20, 2).Quo(params.GoalBonded)). + Mul(params.InflationRateChange). + Mul(math.LegacyNewDecWithPrec(20, 2)), }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, err := types.CalculateReward(tt.blockTime, tt.schedule) - - if tt.expectedError { - require.Error(t, err) - } else { - require.NoError(t, err) - // Denom should never differ - require.Equal(t, tt.expectedCoin.Denom, result.Denom) - // Check diff within 1% - diff := tt.expectedCoin.Amount.Sub(result.Amount) - tolerance := tt.expectedCoin.Amount.Quo(math.NewInt(100)) - require.True(t, diff.LTE(tolerance)) - } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := types.CalculateInflation(tc.bondedRatio, params) + require.True(t, tc.want.Equal(got), "want %s got %s", tc.want, got) + }) + } +} + +func TestCalculateInflationClampsToMax(t *testing.T) { + params := types.DefaultParams() + params.InflationMax = math.LegacyNewDecWithPrec(1, 2) // 0.01, below curve peak + + bonded := params.GoalBonded.QuoInt64(2) + got := types.CalculateInflation(bonded, params) + require.True(t, params.InflationMax.Equal(got)) +} + +// spreadsheetInflationCases are golden vectors from +// "Inflation Calculation (2).xlsx" (goalBonded=0.67, inflationRateChange=0.13). +// wantInflation is the KiiChain column; wantAnnual is TruncateInt(inflation * 1.8e9). +var spreadsheetInflationCases = []struct { + bonded string + wantInflation string + wantAnnual int64 +}{ + {"0", "0.000000000000000000", 0}, + {"0.02", "0.002522388059701493", 4_540_298}, + {"0.04", "0.004889552238805970", 8_801_194}, + {"0.06", "0.007101492537313433", 12_782_686}, + {"0.08", "0.009158208955223881", 16_484_776}, + {"0.1", "0.011059701492537313", 19_907_462}, + {"0.12", "0.012805970149253731", 23_050_746}, + {"0.14", "0.014397014925373134", 25_914_626}, + {"0.16", "0.015832835820895522", 28_499_104}, + {"0.18", "0.017113432835820896", 30_804_179}, + {"0.2", "0.018238805970149254", 32_829_850}, + {"0.22", "0.019208955223880597", 34_576_119}, + {"0.24", "0.020023880597014925", 36_042_985}, + {"0.26", "0.020683582089552239", 37_230_447}, + {"0.28", "0.021188059701492537", 38_138_507}, + {"0.3", "0.021537313432835821", 38_767_164}, + {"0.32", "0.021731343283582090", 39_116_417}, + {"0.34", "0.021770149253731343", 39_186_268}, // spreadsheet peak + {"0.36", "0.021653731343283582", 38_976_716}, + {"0.38", "0.021382089552238806", 38_487_761}, + {"0.4", "0.020955223880597015", 37_719_402}, + {"0.42", "0.020373134328358209", 36_671_641}, + {"0.44", "0.019635820895522388", 35_344_477}, + {"0.46", "0.018743283582089552", 33_737_910}, + {"0.48", "0.017695522388059702", 31_851_940}, // LegacyDec rounding (sheet shows ...701) + {"0.5", "0.016492537313432836", 29_686_567}, + {"0.52", "0.015134328358208955", 27_241_791}, + {"0.54", "0.013620895522388060", 24_517_611}, + {"0.56", "0.011952238805970149", 21_514_029}, + {"0.58", "0.010128358208955224", 18_231_044}, + {"0.6", "0.008149253731343284", 14_668_656}, + {"0.62", "0.006014925373134328", 10_826_865}, + {"0.64", "0.003725373134328358", 6_705_671}, + {"0.66", "0.001280597014925373", 2_305_074}, + {"0.68", "0.000000000000000000", 0}, // above goalBonded + {"0.8", "0.000000000000000000", 0}, + {"1.0", "0.000000000000000000", 0}, +} + +func TestCalculateInflationSpreadsheet(t *testing.T) { + params := types.DefaultParams() + require.True(t, params.GoalBonded.Equal(math.LegacyMustNewDecFromStr("0.67"))) + + for _, tc := range spreadsheetInflationCases { + t.Run("bonded_"+tc.bonded, func(t *testing.T) { + got := types.CalculateInflation(math.LegacyMustNewDecFromStr(tc.bonded), params) + want := math.LegacyMustNewDecFromStr(tc.wantInflation) + require.True(t, want.Equal(got), "bonded=%s want %s got %s", tc.bonded, want, got) }) } } -func TestCalculateRewardNoForcedMinimum(t *testing.T) { - now := time.Now() - denom := "akii" +func TestCalculateRewardSpreadsheetAnnual(t *testing.T) { + // Spreadsheet "Total inflation (KiiChain)" column uses supply_base = 1.8e9 + params := types.DefaultParams() + params.SupplyBase = math.NewInt(1_800_000_000) + + now := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC) + oneYearLater := now.Add(time.Duration(types.SecondsPerYear) * time.Second) - schedule := types.ReleaseSchedule{ - TotalAmount: sdk.NewCoin(denom, math.NewInt(1000000)), - ReleasedAmount: sdk.NewCoin(denom, math.ZeroInt()), - LastReleaseTime: now, - EndTime: now.Add(time.Hour * 24 * 365 * 10), - Active: true, + for _, tc := range spreadsheetInflationCases { + t.Run("bonded_"+tc.bonded, func(t *testing.T) { + bonded := math.LegacyMustNewDecFromStr(tc.bonded) + coin, inf := types.CalculateReward(oneYearLater, now, bonded, params) + + wantInf := math.LegacyMustNewDecFromStr(tc.wantInflation) + require.True(t, wantInf.Equal(inf), "inflation want %s got %s", wantInf, inf) + require.True(t, math.NewInt(tc.wantAnnual).Equal(coin.Amount), + "annual want %d got %s", tc.wantAnnual, coin.Amount) + require.Equal(t, params.TokenDenom, coin.Denom) + }) } +} + +func TestCalculateReward(t *testing.T) { + params := types.DefaultParams() + params.SupplyBase = math.NewInt(1_000_000_000_000) // 1e12 + + now := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC) + bonded := params.GoalBonded.QuoInt64(2) // peak + inflation := types.CalculateInflation(bonded, params) + + t.Run("zero supply base", func(t *testing.T) { + p := params + p.SupplyBase = math.ZeroInt() + coin, inf := types.CalculateReward(now.Add(time.Hour), now, bonded, p) + require.True(t, coin.IsZero()) + require.True(t, inf.IsZero()) + }) + + t.Run("non-positive elapsed", func(t *testing.T) { + coin, inf := types.CalculateReward(now, now, bonded, params) + require.True(t, coin.IsZero()) + require.True(t, inflation.Equal(inf)) + }) + + t.Run("one year elapsed releases annual provision", func(t *testing.T) { + coin, inf := types.CalculateReward( + now.Add(time.Duration(types.SecondsPerYear)*time.Second), + now, + bonded, + params, + ) + require.True(t, inflation.Equal(inf)) + + expected := inflation.MulInt(params.SupplyBase).TruncateInt() + require.True(t, expected.Equal(coin.Amount), "want %s got %s", expected, coin.Amount) + require.Equal(t, params.TokenDenom, coin.Denom) + }) + + t.Run("half year is half annual provision", func(t *testing.T) { + coin, _ := types.CalculateReward( + now.Add(time.Duration(types.SecondsPerYear/2)*time.Second), + now, + bonded, + params, + ) - result, err := types.CalculateReward(now.Add(time.Second), schedule) - require.NoError(t, err) - require.True(t, result.Amount.IsZero(), "expected zero release, got %s", result.Amount) + annual := inflation.MulInt(params.SupplyBase).TruncateInt() + require.True(t, coin.Amount.LTE(annual)) + require.True(t, coin.Amount.GTE(annual.QuoRaw(2).Sub(math.NewInt(1)))) + }) } diff --git a/x/rewards/types/tx.pb.go b/x/rewards/types/tx.pb.go index 25176601..3aaeef40 100644 --- a/x/rewards/types/tx.pb.go +++ b/x/rewards/types/tx.pb.go @@ -16,7 +16,6 @@ import ( grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" - _ "google.golang.org/protobuf/types/known/timestamppb" io "io" math "math" math_bits "math/bits" @@ -222,151 +221,49 @@ func (m *MsgUpdateParamsResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgUpdateParamsResponse proto.InternalMessageInfo -// MsgChangeSchedule is the Msg/ChangeSchedule request type. -type MsgChangeSchedule struct { - // authority is the address of the governance account. - Authority string `protobuf:"bytes,1,opt,name=authority,proto3" json:"authority,omitempty"` - // New information for the schedule - Schedule ReleaseSchedule `protobuf:"bytes,2,opt,name=schedule,proto3" json:"schedule"` -} - -func (m *MsgChangeSchedule) Reset() { *m = MsgChangeSchedule{} } -func (m *MsgChangeSchedule) String() string { return proto.CompactTextString(m) } -func (*MsgChangeSchedule) ProtoMessage() {} -func (*MsgChangeSchedule) Descriptor() ([]byte, []int) { - return fileDescriptor_8e1e54764dba96cb, []int{4} -} -func (m *MsgChangeSchedule) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *MsgChangeSchedule) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgChangeSchedule.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *MsgChangeSchedule) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgChangeSchedule.Merge(m, src) -} -func (m *MsgChangeSchedule) XXX_Size() int { - return m.Size() -} -func (m *MsgChangeSchedule) XXX_DiscardUnknown() { - xxx_messageInfo_MsgChangeSchedule.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgChangeSchedule proto.InternalMessageInfo - -func (m *MsgChangeSchedule) GetAuthority() string { - if m != nil { - return m.Authority - } - return "" -} - -func (m *MsgChangeSchedule) GetSchedule() ReleaseSchedule { - if m != nil { - return m.Schedule - } - return ReleaseSchedule{} -} - -// MsgChangeScheduleResponse defines the response structure for executing a -// MsgChangeSchedule message. -type MsgChangeScheduleResponse struct { -} - -func (m *MsgChangeScheduleResponse) Reset() { *m = MsgChangeScheduleResponse{} } -func (m *MsgChangeScheduleResponse) String() string { return proto.CompactTextString(m) } -func (*MsgChangeScheduleResponse) ProtoMessage() {} -func (*MsgChangeScheduleResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_8e1e54764dba96cb, []int{5} -} -func (m *MsgChangeScheduleResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *MsgChangeScheduleResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgChangeScheduleResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *MsgChangeScheduleResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgChangeScheduleResponse.Merge(m, src) -} -func (m *MsgChangeScheduleResponse) XXX_Size() int { - return m.Size() -} -func (m *MsgChangeScheduleResponse) XXX_DiscardUnknown() { - xxx_messageInfo_MsgChangeScheduleResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgChangeScheduleResponse proto.InternalMessageInfo - func init() { proto.RegisterType((*MsgFundPool)(nil), "kiichain.rewards.v1beta1.MsgFundPool") proto.RegisterType((*MsgFundPoolResponse)(nil), "kiichain.rewards.v1beta1.MsgFundPoolResponse") proto.RegisterType((*MsgUpdateParams)(nil), "kiichain.rewards.v1beta1.MsgUpdateParams") proto.RegisterType((*MsgUpdateParamsResponse)(nil), "kiichain.rewards.v1beta1.MsgUpdateParamsResponse") - proto.RegisterType((*MsgChangeSchedule)(nil), "kiichain.rewards.v1beta1.MsgChangeSchedule") - proto.RegisterType((*MsgChangeScheduleResponse)(nil), "kiichain.rewards.v1beta1.MsgChangeScheduleResponse") } func init() { proto.RegisterFile("kiichain/rewards/v1beta1/tx.proto", fileDescriptor_8e1e54764dba96cb) } var fileDescriptor_8e1e54764dba96cb = []byte{ - // 610 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0x3f, 0x6f, 0xd3, 0x40, - 0x18, 0xc6, 0xe3, 0x02, 0x51, 0x7b, 0xe1, 0x8f, 0x62, 0x5a, 0x25, 0x31, 0x92, 0x93, 0x5a, 0x44, - 0x4a, 0x02, 0xb1, 0x95, 0x56, 0x62, 0xc8, 0x80, 0x44, 0x2a, 0x75, 0x41, 0x91, 0x2a, 0x57, 0x48, - 0x88, 0xa5, 0x5c, 0xec, 0xeb, 0xc5, 0xc2, 0xbe, 0xb3, 0x7c, 0xe7, 0xd2, 0x6c, 0x88, 0x91, 0x89, - 0x99, 0x4f, 0x11, 0x09, 0xf8, 0x06, 0x0c, 0x1d, 0x23, 0x26, 0xa6, 0x0a, 0x25, 0x43, 0x76, 0x3e, - 0x01, 0xb2, 0x7d, 0x76, 0xd2, 0x56, 0xa9, 0x0a, 0x4b, 0x62, 0xfb, 0x7e, 0xf7, 0x3e, 0xcf, 0xf3, - 0xde, 0xe9, 0x05, 0xdb, 0xef, 0x1c, 0xc7, 0x1a, 0x42, 0x87, 0x18, 0x01, 0x7a, 0x0f, 0x03, 0x9b, - 0x19, 0x27, 0x9d, 0x01, 0xe2, 0xb0, 0x63, 0xf0, 0x53, 0xdd, 0x0f, 0x28, 0xa7, 0x72, 0x39, 0x45, - 0x74, 0x81, 0xe8, 0x02, 0x51, 0x36, 0x31, 0xc5, 0x34, 0x86, 0x8c, 0xe8, 0x29, 0xe1, 0x15, 0xd5, - 0xa2, 0xcc, 0xa3, 0xcc, 0x18, 0x40, 0x86, 0xb2, 0x6a, 0x16, 0x75, 0x88, 0x58, 0xaf, 0xaf, 0x94, - 0xf4, 0x61, 0x00, 0x3d, 0x26, 0xb0, 0xc7, 0xab, 0x9d, 0x8d, 0x7c, 0x94, 0x52, 0x55, 0x4c, 0x29, - 0x76, 0x91, 0x11, 0xbf, 0x0d, 0xc2, 0x63, 0x83, 0x3b, 0x1e, 0x62, 0x1c, 0x7a, 0xbe, 0x00, 0x4a, - 0xc2, 0x8d, 0xc7, 0xb0, 0x71, 0xd2, 0x89, 0xfe, 0xc4, 0x42, 0x25, 0x59, 0x38, 0x4a, 0xfc, 0x27, - 0x2f, 0x62, 0xa9, 0x08, 0x3d, 0x87, 0x50, 0x23, 0xfe, 0x4d, 0x3e, 0x69, 0xdf, 0x25, 0x50, 0xe8, - 0x33, 0xbc, 0x1f, 0x12, 0xfb, 0x80, 0x52, 0x57, 0x6e, 0x82, 0x3c, 0x43, 0xc4, 0x46, 0x41, 0x59, - 0xaa, 0x49, 0x8d, 0x8d, 0x5e, 0xf1, 0xcf, 0x79, 0xf5, 0xde, 0x08, 0x7a, 0x6e, 0x57, 0x4b, 0xbe, - 0x6b, 0xa6, 0x00, 0xe4, 0xd7, 0x20, 0x0f, 0x3d, 0x1a, 0x12, 0x5e, 0x5e, 0xab, 0x49, 0x8d, 0xc2, - 0x4e, 0x45, 0x17, 0x62, 0x51, 0x83, 0xd2, 0x5e, 0xea, 0x7b, 0xd4, 0x21, 0xbd, 0xfa, 0xd9, 0x79, - 0x35, 0xb7, 0xa8, 0x94, 0x6c, 0xd3, 0xbe, 0xcc, 0xc7, 0xad, 0x82, 0x8b, 0x30, 0xb4, 0x46, 0x47, - 0x51, 0x1f, 0x4d, 0x51, 0xaf, 0xbb, 0xfd, 0x71, 0x3e, 0x6e, 0x09, 0x99, 0x4f, 0xf3, 0x71, 0xab, - 0x98, 0x76, 0xea, 0x38, 0x24, 0x76, 0xdb, 0xa7, 0xd4, 0xd5, 0xb6, 0xc0, 0xc3, 0x25, 0xdb, 0x26, - 0x62, 0x3e, 0x25, 0x0c, 0x69, 0x5f, 0x25, 0xf0, 0xa0, 0xcf, 0xf0, 0x2b, 0xdf, 0x86, 0x1c, 0x1d, - 0xc4, 0x6d, 0x97, 0x9f, 0x81, 0x0d, 0x18, 0xf2, 0x21, 0x0d, 0x1c, 0x3e, 0x12, 0xa9, 0xca, 0x3f, - 0xbf, 0xb5, 0x37, 0x85, 0xdb, 0x17, 0xb6, 0x1d, 0x20, 0xc6, 0x0e, 0x79, 0xe0, 0x10, 0x6c, 0x2e, - 0x50, 0xf9, 0x39, 0xc8, 0x27, 0x07, 0x27, 0xf2, 0xd5, 0xf4, 0x55, 0x17, 0x46, 0x4f, 0x94, 0x7a, - 0xb7, 0xa3, 0x98, 0xa6, 0xd8, 0xd5, 0x6d, 0x44, 0x29, 0x16, 0xf5, 0xa2, 0x20, 0x5b, 0x69, 0x90, - 0x30, 0x36, 0xd8, 0x4e, 0x48, 0xad, 0x02, 0x4a, 0x97, 0x4c, 0x67, 0x81, 0x7e, 0x48, 0xa0, 0xd8, - 0x67, 0x78, 0x6f, 0x08, 0x09, 0x46, 0x87, 0xd6, 0x10, 0xd9, 0xa1, 0x8b, 0xfe, 0x3b, 0xd2, 0x4b, - 0xb0, 0xce, 0x44, 0x0d, 0x11, 0xaa, 0xb9, 0x3a, 0x94, 0x89, 0x5c, 0x04, 0x59, 0x26, 0x2a, 0xd2, - 0x65, 0x05, 0xba, 0xad, 0xab, 0xf9, 0x4a, 0x69, 0x3e, 0x2b, 0xf6, 0xdb, 0x4e, 0x59, 0xed, 0x11, - 0xa8, 0x5c, 0x49, 0x91, 0x66, 0xdc, 0x99, 0xac, 0x81, 0x5b, 0x7d, 0x86, 0xe5, 0xb7, 0x60, 0x3d, - 0xbb, 0x87, 0xf5, 0xd5, 0xbe, 0x96, 0xce, 0x5d, 0x69, 0xdf, 0x08, 0x4b, 0x95, 0x64, 0x17, 0xdc, - 0xbd, 0x70, 0x35, 0x9a, 0xd7, 0x6e, 0x5f, 0x46, 0x95, 0xce, 0x8d, 0xd1, 0x4c, 0x2d, 0x00, 0xf7, - 0x2f, 0x9d, 0xdb, 0x93, 0x6b, 0x8b, 0x5c, 0x84, 0x95, 0xdd, 0x7f, 0x80, 0x53, 0x4d, 0xe5, 0xce, - 0x87, 0xf9, 0xb8, 0x25, 0xf5, 0xf6, 0xcf, 0xa6, 0xaa, 0x34, 0x99, 0xaa, 0xd2, 0xef, 0xa9, 0x2a, - 0x7d, 0x9e, 0xa9, 0xb9, 0xc9, 0x4c, 0xcd, 0xfd, 0x9a, 0xa9, 0xb9, 0x37, 0x4f, 0xb1, 0xc3, 0x87, - 0xe1, 0x40, 0xb7, 0xa8, 0x67, 0x64, 0x93, 0x28, 0x7b, 0x38, 0xcd, 0x86, 0x52, 0x3c, 0x8c, 0x06, - 0xf9, 0x78, 0x4a, 0xec, 0xfe, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x64, 0xcc, 0x15, 0x84, 0x4f, 0x05, - 0x00, 0x00, + // 506 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x93, 0x4f, 0x8b, 0xd3, 0x40, + 0x18, 0xc6, 0x3b, 0xfe, 0x29, 0xee, 0x54, 0x91, 0xc6, 0x5d, 0xb6, 0xcd, 0x21, 0xdb, 0x0d, 0x14, + 0xba, 0xc5, 0x26, 0x74, 0x05, 0x0f, 0x3d, 0x08, 0x56, 0xd8, 0x5b, 0x61, 0x89, 0x08, 0xe2, 0x65, + 0x9d, 0x26, 0xe3, 0x74, 0x30, 0x99, 0x09, 0x99, 0xc9, 0xba, 0xbd, 0x89, 0x47, 0x4f, 0x9e, 0xfd, + 0x14, 0x05, 0xf5, 0x3b, 0xec, 0x71, 0xf1, 0xe4, 0x41, 0x16, 0x69, 0x0f, 0xbd, 0xfb, 0x09, 0x24, + 0x99, 0x49, 0x5a, 0x85, 0x4a, 0x2f, 0xed, 0x74, 0xde, 0xdf, 0xbc, 0xcf, 0xfb, 0x3c, 0xd3, 0x81, + 0x87, 0x6f, 0x29, 0xf5, 0x27, 0x88, 0x32, 0x37, 0xc1, 0xef, 0x50, 0x12, 0x08, 0xf7, 0xbc, 0x3f, + 0xc6, 0x12, 0xf5, 0x5d, 0x79, 0xe1, 0xc4, 0x09, 0x97, 0xdc, 0x68, 0x14, 0x88, 0xa3, 0x11, 0x47, + 0x23, 0xe6, 0x2e, 0xe1, 0x84, 0xe7, 0x90, 0x9b, 0xad, 0x14, 0x6f, 0x5a, 0x3e, 0x17, 0x11, 0x17, + 0xee, 0x18, 0x09, 0x5c, 0x76, 0xf3, 0x39, 0x65, 0xba, 0xde, 0xde, 0x28, 0x19, 0xa3, 0x04, 0x45, + 0x42, 0x63, 0xfb, 0xba, 0x4d, 0x24, 0x88, 0x7b, 0xde, 0xcf, 0xbe, 0x74, 0xa1, 0xa9, 0x0a, 0x67, + 0x4a, 0x58, 0xfd, 0xd0, 0xa5, 0x3a, 0x8a, 0x28, 0xe3, 0x6e, 0xfe, 0xa9, 0xb6, 0xec, 0x6f, 0x00, + 0xd6, 0x46, 0x82, 0x9c, 0xa4, 0x2c, 0x38, 0xe5, 0x3c, 0x34, 0x8e, 0x60, 0x55, 0x60, 0x16, 0xe0, + 0xa4, 0x01, 0x5a, 0xa0, 0xb3, 0x33, 0xac, 0xff, 0xbe, 0x3e, 0xb8, 0x37, 0x45, 0x51, 0x38, 0xb0, + 0xd5, 0xbe, 0xed, 0x69, 0xc0, 0x78, 0x09, 0xab, 0x28, 0xe2, 0x29, 0x93, 0x8d, 0x1b, 0x2d, 0xd0, + 0xa9, 0x1d, 0x37, 0x1d, 0x2d, 0x96, 0x39, 0x2b, 0x42, 0x70, 0x9e, 0x71, 0xca, 0x86, 0xed, 0xcb, + 0xeb, 0x83, 0xca, 0xaa, 0x93, 0x3a, 0x66, 0x7f, 0x5e, 0xce, 0xba, 0xb5, 0x10, 0x13, 0xe4, 0x4f, + 0xcf, 0xb2, 0x00, 0x3c, 0xdd, 0x6f, 0x70, 0xf8, 0x61, 0x39, 0xeb, 0x6a, 0x99, 0x8f, 0xcb, 0x59, + 0xb7, 0x5e, 0x24, 0xf1, 0x26, 0x65, 0x41, 0x2f, 0xe6, 0x3c, 0xb4, 0xf7, 0xe0, 0x83, 0xb5, 0xb1, + 0x3d, 0x2c, 0x62, 0xce, 0x04, 0xb6, 0xbf, 0x00, 0x78, 0x7f, 0x24, 0xc8, 0x8b, 0x38, 0x40, 0x12, + 0x9f, 0xe6, 0x79, 0x19, 0x8f, 0xe1, 0x0e, 0x4a, 0xe5, 0x84, 0x27, 0x54, 0x4e, 0xb5, 0xab, 0xc6, + 0xf7, 0xaf, 0xbd, 0x5d, 0x3d, 0xed, 0xd3, 0x20, 0x48, 0xb0, 0x10, 0xcf, 0x65, 0x42, 0x19, 0xf1, + 0x56, 0xa8, 0xf1, 0x04, 0x56, 0x55, 0xe2, 0xda, 0x5f, 0xcb, 0xd9, 0x74, 0xd3, 0x8e, 0x52, 0x1a, + 0xde, 0xca, 0x6c, 0x7a, 0xfa, 0xd4, 0xa0, 0x93, 0xb9, 0x58, 0xf5, 0xcb, 0x8c, 0xec, 0x15, 0x46, + 0xd2, 0x7c, 0xc0, 0x9e, 0x22, 0xed, 0x26, 0xdc, 0xff, 0x67, 0xe8, 0xc2, 0xd0, 0xf1, 0x4f, 0x00, + 0x6f, 0x8e, 0x04, 0x31, 0x5e, 0xc3, 0x3b, 0xe5, 0x1d, 0xb5, 0x37, 0x0f, 0xb2, 0x96, 0x89, 0xd9, + 0xdb, 0x0a, 0x2b, 0x94, 0x8c, 0x10, 0xde, 0xfd, 0x2b, 0xb6, 0xa3, 0xff, 0x1e, 0x5f, 0x47, 0xcd, + 0xfe, 0xd6, 0x68, 0xa1, 0x66, 0xde, 0x7e, 0xbf, 0x9c, 0x75, 0xc1, 0xf0, 0xe4, 0x72, 0x6e, 0x81, + 0xab, 0xb9, 0x05, 0x7e, 0xcd, 0x2d, 0xf0, 0x69, 0x61, 0x55, 0xae, 0x16, 0x56, 0xe5, 0xc7, 0xc2, + 0xaa, 0xbc, 0x7a, 0x48, 0xa8, 0x9c, 0xa4, 0x63, 0xc7, 0xe7, 0x91, 0x5b, 0xbe, 0x88, 0x72, 0x71, + 0x51, 0x3e, 0x0e, 0x39, 0x8d, 0xb1, 0x18, 0x57, 0xf3, 0x7f, 0xf3, 0xa3, 0x3f, 0x01, 0x00, 0x00, + 0xff, 0xff, 0x95, 0x60, 0xe1, 0x11, 0xb0, 0x03, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -381,16 +278,13 @@ const _ = grpc.SupportPackageIsVersion4 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type MsgClient interface { - // FundPool adds funds to the community pool that can be used on a extension + // FundPool adds funds to the community pool FundPool(ctx context.Context, in *MsgFundPool, opts ...grpc.CallOption) (*MsgFundPoolResponse, error) // UpdateParams defines a governance operation for updating the x/rewards module // parameters. The authority is hard-coded to the x/gov module account. // // Since: cosmos-sdk 0.47 UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) - // ChangeSchedule defines a governance operation for changing the reward and - // its schedule - ChangeSchedule(ctx context.Context, in *MsgChangeSchedule, opts ...grpc.CallOption) (*MsgChangeScheduleResponse, error) } type msgClient struct { @@ -419,27 +313,15 @@ func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts return out, nil } -func (c *msgClient) ChangeSchedule(ctx context.Context, in *MsgChangeSchedule, opts ...grpc.CallOption) (*MsgChangeScheduleResponse, error) { - out := new(MsgChangeScheduleResponse) - err := c.cc.Invoke(ctx, "/kiichain.rewards.v1beta1.Msg/ChangeSchedule", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - // MsgServer is the server API for Msg service. type MsgServer interface { - // FundPool adds funds to the community pool that can be used on a extension + // FundPool adds funds to the community pool FundPool(context.Context, *MsgFundPool) (*MsgFundPoolResponse, error) // UpdateParams defines a governance operation for updating the x/rewards module // parameters. The authority is hard-coded to the x/gov module account. // // Since: cosmos-sdk 0.47 UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) - // ChangeSchedule defines a governance operation for changing the reward and - // its schedule - ChangeSchedule(context.Context, *MsgChangeSchedule) (*MsgChangeScheduleResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -452,9 +334,6 @@ func (*UnimplementedMsgServer) FundPool(ctx context.Context, req *MsgFundPool) ( func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") } -func (*UnimplementedMsgServer) ChangeSchedule(ctx context.Context, req *MsgChangeSchedule) (*MsgChangeScheduleResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method ChangeSchedule not implemented") -} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -496,24 +375,7 @@ func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(in return interceptor(ctx, in, info, handler) } -func _Msg_ChangeSchedule_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgChangeSchedule) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MsgServer).ChangeSchedule(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/kiichain.rewards.v1beta1.Msg/ChangeSchedule", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).ChangeSchedule(ctx, req.(*MsgChangeSchedule)) - } - return interceptor(ctx, in, info, handler) -} - +var Msg_serviceDesc = _Msg_serviceDesc var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "kiichain.rewards.v1beta1.Msg", HandlerType: (*MsgServer)(nil), @@ -526,10 +388,6 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "UpdateParams", Handler: _Msg_UpdateParams_Handler, }, - { - MethodName: "ChangeSchedule", - Handler: _Msg_ChangeSchedule_Handler, - }, }, Streams: []grpc.StreamDesc{}, Metadata: "kiichain/rewards/v1beta1/tx.proto", @@ -661,69 +519,6 @@ func (m *MsgUpdateParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) return len(dAtA) - i, nil } -func (m *MsgChangeSchedule) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgChangeSchedule) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgChangeSchedule) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size, err := m.Schedule.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTx(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - if len(m.Authority) > 0 { - i -= len(m.Authority) - copy(dAtA[i:], m.Authority) - i = encodeVarintTx(dAtA, i, uint64(len(m.Authority))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *MsgChangeScheduleResponse) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgChangeScheduleResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgChangeScheduleResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - return len(dAtA) - i, nil -} - func encodeVarintTx(dAtA []byte, offset int, v uint64) int { offset -= sovTx(v) base := offset @@ -783,30 +578,6 @@ func (m *MsgUpdateParamsResponse) Size() (n int) { return n } -func (m *MsgChangeSchedule) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Authority) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - l = m.Schedule.Size() - n += 1 + l + sovTx(uint64(l)) - return n -} - -func (m *MsgChangeScheduleResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - return n -} - func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -1143,171 +914,6 @@ func (m *MsgUpdateParamsResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgChangeSchedule) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgChangeSchedule: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgChangeSchedule: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Authority", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Authority = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Schedule", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Schedule.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *MsgChangeScheduleResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgChangeScheduleResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgChangeScheduleResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/rewards/types/types.pb.go b/x/rewards/types/types.pb.go index 7a72af73..d8ffb385 100644 --- a/x/rewards/types/types.pb.go +++ b/x/rewards/types/types.pb.go @@ -30,98 +30,20 @@ var _ = time.Kitchen // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -// ReleaseSchedule defines information related to reward distribution -type ReleaseSchedule struct { - // Total amount to be rewarded - TotalAmount types.Coin `protobuf:"bytes,1,opt,name=total_amount,json=totalAmount,proto3" json:"total_amount" yaml:"total_amount"` - // Amount released - ReleasedAmount types.Coin `protobuf:"bytes,2,opt,name=released_amount,json=releasedAmount,proto3" json:"released_amount" yaml:"released_amount"` - // Timestamp of end of release - EndTime time.Time `protobuf:"bytes,3,opt,name=end_time,json=endTime,proto3,stdtime" json:"end_time" yaml:"end_time"` - // Last height released - LastReleaseTime time.Time `protobuf:"bytes,5,opt,name=last_release_time,json=lastReleaseTime,proto3,stdtime" json:"last_release_time" yaml:"last_release_time"` - // If reward pool is active - Active bool `protobuf:"varint,6,opt,name=active,proto3" json:"active,omitempty" yaml:"active"` -} - -func (m *ReleaseSchedule) Reset() { *m = ReleaseSchedule{} } -func (m *ReleaseSchedule) String() string { return proto.CompactTextString(m) } -func (*ReleaseSchedule) ProtoMessage() {} -func (*ReleaseSchedule) Descriptor() ([]byte, []int) { - return fileDescriptor_890c6773eb163743, []int{0} -} -func (m *ReleaseSchedule) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *ReleaseSchedule) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_ReleaseSchedule.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *ReleaseSchedule) XXX_Merge(src proto.Message) { - xxx_messageInfo_ReleaseSchedule.Merge(m, src) -} -func (m *ReleaseSchedule) XXX_Size() int { - return m.Size() -} -func (m *ReleaseSchedule) XXX_DiscardUnknown() { - xxx_messageInfo_ReleaseSchedule.DiscardUnknown(m) -} - -var xxx_messageInfo_ReleaseSchedule proto.InternalMessageInfo - -func (m *ReleaseSchedule) GetTotalAmount() types.Coin { - if m != nil { - return m.TotalAmount - } - return types.Coin{} -} - -func (m *ReleaseSchedule) GetReleasedAmount() types.Coin { - if m != nil { - return m.ReleasedAmount - } - return types.Coin{} -} - -func (m *ReleaseSchedule) GetEndTime() time.Time { - if m != nil { - return m.EndTime - } - return time.Time{} -} - -func (m *ReleaseSchedule) GetLastReleaseTime() time.Time { - if m != nil { - return m.LastReleaseTime - } - return time.Time{} -} - -func (m *ReleaseSchedule) GetActive() bool { - if m != nil { - return m.Active - } - return false -} - // RewardPool is the global fee pool for distribution. type RewardPool struct { CommunityPool github_com_cosmos_cosmos_sdk_types.DecCoins `protobuf:"bytes,1,rep,name=community_pool,json=communityPool,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.DecCoins" json:"community_pool"` + // Timestamp of the last emission release + LastReleaseTime time.Time `protobuf:"bytes,2,opt,name=last_release_time,json=lastReleaseTime,proto3,stdtime" json:"last_release_time" yaml:"last_release_time"` + // Cumulative amount released from the pool (observability only) + TotalReleased types.Coin `protobuf:"bytes,3,opt,name=total_released,json=totalReleased,proto3" json:"total_released" yaml:"total_released"` } func (m *RewardPool) Reset() { *m = RewardPool{} } func (m *RewardPool) String() string { return proto.CompactTextString(m) } func (*RewardPool) ProtoMessage() {} func (*RewardPool) Descriptor() ([]byte, []int) { - return fileDescriptor_890c6773eb163743, []int{1} + return fileDescriptor_890c6773eb163743, []int{0} } func (m *RewardPool) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -157,8 +79,21 @@ func (m *RewardPool) GetCommunityPool() github_com_cosmos_cosmos_sdk_types.DecCo return nil } +func (m *RewardPool) GetLastReleaseTime() time.Time { + if m != nil { + return m.LastReleaseTime + } + return time.Time{} +} + +func (m *RewardPool) GetTotalReleased() types.Coin { + if m != nil { + return m.TotalReleased + } + return types.Coin{} +} + func init() { - proto.RegisterType((*ReleaseSchedule)(nil), "kiichain.rewards.v1beta1.ReleaseSchedule") proto.RegisterType((*RewardPool)(nil), "kiichain.rewards.v1beta1.RewardPool") } @@ -167,41 +102,35 @@ func init() { } var fileDescriptor_890c6773eb163743 = []byte{ - // 483 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x93, 0x3f, 0x8f, 0xd3, 0x3e, - 0x18, 0xc7, 0xeb, 0x5f, 0xf5, 0x2b, 0x27, 0x97, 0xbb, 0xaa, 0x01, 0xa1, 0x50, 0x50, 0x52, 0x45, - 0x37, 0x94, 0x7f, 0x8e, 0xee, 0x58, 0x10, 0x1b, 0x05, 0x31, 0xa3, 0xc0, 0x02, 0x4b, 0xe5, 0x24, - 0x26, 0xb5, 0xce, 0xce, 0x53, 0xd5, 0xce, 0x41, 0x07, 0x5e, 0x01, 0xcb, 0xbd, 0x0c, 0xc4, 0xc4, - 0x1b, 0x60, 0xbf, 0xf1, 0x46, 0xa6, 0x1e, 0x6a, 0x07, 0xf6, 0x7b, 0x05, 0x28, 0xb6, 0x13, 0x9d, - 0x00, 0xe9, 0x96, 0xc4, 0x7e, 0xf4, 0x7c, 0x3f, 0xdf, 0x27, 0xdf, 0xd8, 0x78, 0xff, 0x88, 0xf3, - 0x6c, 0x4e, 0x79, 0x19, 0x2f, 0xd9, 0x07, 0xba, 0xcc, 0x55, 0x7c, 0x7c, 0x90, 0x32, 0x4d, 0x0f, - 0x62, 0xbd, 0x5a, 0x30, 0x45, 0x16, 0x4b, 0xd0, 0xe0, 0xf9, 0x4d, 0x17, 0x71, 0x5d, 0xc4, 0x75, - 0x8d, 0x82, 0x0c, 0x94, 0x04, 0x15, 0xa7, 0x54, 0xb1, 0x56, 0x9a, 0x01, 0x2f, 0xad, 0x72, 0x74, - 0xb3, 0x80, 0x02, 0xcc, 0x32, 0xae, 0x57, 0xae, 0x1a, 0x16, 0x00, 0x85, 0x60, 0xb1, 0xd9, 0xa5, - 0xd5, 0xfb, 0x58, 0x73, 0xc9, 0x94, 0xa6, 0x72, 0xe1, 0x1a, 0x86, 0x54, 0xf2, 0x12, 0x62, 0xf3, - 0xb4, 0xa5, 0xe8, 0x7b, 0x17, 0x0f, 0x12, 0x26, 0x18, 0x55, 0xec, 0x75, 0x36, 0x67, 0x79, 0x25, - 0x98, 0xf7, 0x16, 0x5f, 0xd7, 0xa0, 0xa9, 0x98, 0x51, 0x09, 0x55, 0xa9, 0x7d, 0x34, 0x46, 0x93, - 0xfe, 0xe1, 0x6d, 0x62, 0x87, 0x22, 0xf5, 0x50, 0xcd, 0xa4, 0xe4, 0x39, 0xf0, 0x72, 0x7a, 0xe7, - 0x74, 0x1d, 0x76, 0x2e, 0xd6, 0xe1, 0x8d, 0x15, 0x95, 0xe2, 0x69, 0x74, 0x59, 0x1c, 0x25, 0x7d, - 0xb3, 0x7d, 0x66, 0x76, 0x5e, 0x8a, 0x07, 0x4b, 0xeb, 0x96, 0x37, 0xf4, 0xff, 0xae, 0xa2, 0x07, - 0x8e, 0x7e, 0xcb, 0xd2, 0xff, 0xd0, 0x47, 0xc9, 0x5e, 0x53, 0x71, 0x1e, 0x09, 0xde, 0x61, 0x65, - 0x3e, 0xab, 0x3f, 0xde, 0xef, 0x1a, 0xf8, 0x88, 0xd8, 0x64, 0x48, 0x93, 0x0c, 0x79, 0xd3, 0x24, - 0xd3, 0xce, 0x3e, 0xb0, 0xf4, 0x46, 0x19, 0x9d, 0x9c, 0x87, 0x28, 0xb9, 0xc6, 0xca, 0xbc, 0x6e, - 0xf5, 0x04, 0x1e, 0x0a, 0xaa, 0xf4, 0xcc, 0x59, 0x59, 0xf8, 0xff, 0x57, 0xc2, 0xf7, 0x1d, 0xdc, - 0xb7, 0xf0, 0xbf, 0x10, 0xd6, 0x65, 0x50, 0xd7, 0xdd, 0x4f, 0x30, 0x6e, 0xf7, 0x70, 0x8f, 0x66, - 0x9a, 0x1f, 0x33, 0xbf, 0x37, 0x46, 0x93, 0x9d, 0xe9, 0xf0, 0x62, 0x1d, 0xee, 0x5a, 0x84, 0xad, - 0x47, 0x89, 0x6b, 0x88, 0x3e, 0x23, 0x8c, 0x13, 0x73, 0x7a, 0x5e, 0x01, 0x08, 0xef, 0x13, 0xde, - 0xcb, 0x40, 0xca, 0xaa, 0xe4, 0x7a, 0x35, 0x5b, 0x00, 0x08, 0x1f, 0x8d, 0xbb, 0x93, 0xfe, 0xe1, - 0xdd, 0x7f, 0xc6, 0xfb, 0x82, 0x65, 0x26, 0xe1, 0x27, 0xf5, 0x98, 0x5f, 0xcf, 0xc3, 0x07, 0x05, - 0xd7, 0xf3, 0x2a, 0x25, 0x19, 0xc8, 0xd8, 0x9d, 0x40, 0xfb, 0x7a, 0xa4, 0xf2, 0x23, 0x77, 0x74, - 0x9d, 0x46, 0x7d, 0xf9, 0xf5, 0xed, 0x3e, 0x4a, 0x76, 0x5b, 0xb7, 0xda, 0x7e, 0xfa, 0xf2, 0x74, - 0x13, 0xa0, 0xb3, 0x4d, 0x80, 0x7e, 0x6e, 0x02, 0x74, 0xb2, 0x0d, 0x3a, 0x67, 0xdb, 0xa0, 0xf3, - 0x63, 0x1b, 0x74, 0xde, 0x3d, 0xbc, 0x84, 0x6e, 0x2f, 0x47, 0xbb, 0xf8, 0xd8, 0xde, 0x13, 0x63, - 0x92, 0xf6, 0x4c, 0x96, 0x8f, 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0x75, 0x10, 0x1e, 0x59, 0x48, - 0x03, 0x00, 0x00, + // 388 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x92, 0xbf, 0xae, 0xd3, 0x30, + 0x18, 0xc5, 0x93, 0x56, 0x62, 0x48, 0xd5, 0xa2, 0x56, 0x20, 0x85, 0x0a, 0x92, 0x2a, 0xea, 0x50, + 0xf1, 0xc7, 0x56, 0xcb, 0x82, 0x18, 0x0b, 0x62, 0x46, 0x11, 0x13, 0x4b, 0xe4, 0x24, 0x26, 0xb5, + 0x6a, 0xe7, 0x8b, 0x62, 0x17, 0xe8, 0xc0, 0x3b, 0xf4, 0x31, 0x10, 0x13, 0x8f, 0xd1, 0xb1, 0x23, + 0x62, 0x68, 0x51, 0x3b, 0xb0, 0xdf, 0x27, 0xb8, 0x8a, 0xe3, 0x44, 0xf7, 0xea, 0xde, 0x25, 0xf9, + 0x72, 0x74, 0x4e, 0xce, 0xf7, 0xb3, 0xec, 0x4c, 0xd7, 0x8c, 0x25, 0x2b, 0xc2, 0x72, 0x5c, 0xd2, + 0x6f, 0xa4, 0x4c, 0x25, 0xfe, 0x3a, 0x8f, 0xa9, 0x22, 0x73, 0xac, 0xb6, 0x05, 0x95, 0xa8, 0x28, + 0x41, 0xc1, 0xc8, 0x6d, 0x5c, 0xc8, 0xb8, 0x90, 0x71, 0x8d, 0xbd, 0x04, 0xa4, 0x00, 0x89, 0x63, + 0x22, 0x69, 0x1b, 0x4d, 0x80, 0xe5, 0x75, 0x72, 0xfc, 0x28, 0x83, 0x0c, 0xf4, 0x88, 0xab, 0xc9, + 0xa8, 0x7e, 0x06, 0x90, 0x71, 0x8a, 0xf5, 0x57, 0xbc, 0xf9, 0x82, 0x15, 0x13, 0x54, 0x2a, 0x22, + 0x0a, 0x63, 0x18, 0x12, 0xc1, 0x72, 0xc0, 0xfa, 0x59, 0x4b, 0xc1, 0xdf, 0x8e, 0xe3, 0x84, 0xba, + 0xfd, 0x23, 0x00, 0x1f, 0xfd, 0x70, 0x06, 0x09, 0x08, 0xb1, 0xc9, 0x99, 0xda, 0x46, 0x05, 0x00, + 0x77, 0xed, 0x49, 0x77, 0xd6, 0x5b, 0x3c, 0x45, 0xf5, 0x46, 0xa8, 0xda, 0xa8, 0x59, 0x13, 0xbd, + 0xa7, 0xc9, 0x3b, 0x60, 0xf9, 0xf2, 0xcd, 0xfe, 0xe8, 0x5b, 0xbf, 0x4e, 0xfe, 0x8b, 0x8c, 0xa9, + 0xd5, 0x26, 0x46, 0x09, 0x08, 0x6c, 0x08, 0xea, 0xd7, 0x2b, 0x99, 0xae, 0x0d, 0xba, 0xc9, 0xc8, + 0x9f, 0xff, 0x7f, 0x3f, 0xb7, 0xc3, 0x7e, 0xdb, 0xa6, 0xeb, 0xb9, 0x33, 0xe4, 0x44, 0xaa, 0xa8, + 0xa4, 0x9c, 0x12, 0x49, 0xa3, 0x0a, 0xc0, 0xed, 0x4c, 0xec, 0x59, 0x6f, 0x31, 0x46, 0x35, 0x1d, + 0x6a, 0xe8, 0xd0, 0xa7, 0x86, 0x6e, 0x39, 0xad, 0xfa, 0xaf, 0x8e, 0xbe, 0xbb, 0x25, 0x82, 0xbf, + 0x0d, 0xee, 0xfc, 0x22, 0xd8, 0x9d, 0x7c, 0x3b, 0x7c, 0x58, 0xe9, 0x61, 0x2d, 0x57, 0xd9, 0x51, + 0xe4, 0x0c, 0x14, 0x28, 0xc2, 0x1b, 0x6f, 0xea, 0x76, 0x75, 0xd5, 0x93, 0x7b, 0x61, 0x35, 0xe9, + 0x33, 0xd3, 0xf4, 0xb8, 0x6e, 0xba, 0x1d, 0x0f, 0xc2, 0xbe, 0x16, 0x4c, 0x47, 0xba, 0xfc, 0xb0, + 0x3f, 0x7b, 0xf6, 0xe1, 0xec, 0xd9, 0xff, 0xce, 0x9e, 0xbd, 0xbb, 0x78, 0xd6, 0xe1, 0xe2, 0x59, + 0x7f, 0x2e, 0x9e, 0xf5, 0xf9, 0xe5, 0x8d, 0x93, 0x6a, 0xef, 0x4a, 0x3b, 0x7c, 0x6f, 0xaf, 0x8d, + 0x3e, 0xb3, 0xf8, 0x81, 0x66, 0x7e, 0x7d, 0x1d, 0x00, 0x00, 0xff, 0xff, 0xc7, 0x5e, 0x72, 0x35, + 0x57, 0x02, 0x00, 0x00, } -func (m *ReleaseSchedule) Marshal() (dAtA []byte, err error) { +func (m *RewardPool) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -211,44 +140,18 @@ func (m *ReleaseSchedule) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ReleaseSchedule) MarshalTo(dAtA []byte) (int, error) { +func (m *RewardPool) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ReleaseSchedule) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *RewardPool) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.Active { - i-- - if m.Active { - dAtA[i] = 1 - } else { - dAtA[i] = 0 - } - i-- - dAtA[i] = 0x30 - } - n1, err1 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(m.LastReleaseTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(m.LastReleaseTime):]) - if err1 != nil { - return 0, err1 - } - i -= n1 - i = encodeVarintTypes(dAtA, i, uint64(n1)) - i-- - dAtA[i] = 0x2a - n2, err2 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(m.EndTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(m.EndTime):]) - if err2 != nil { - return 0, err2 - } - i -= n2 - i = encodeVarintTypes(dAtA, i, uint64(n2)) - i-- - dAtA[i] = 0x1a { - size, err := m.ReleasedAmount.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.TotalReleased.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -256,40 +159,15 @@ func (m *ReleaseSchedule) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintTypes(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x12 - { - size, err := m.TotalAmount.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTypes(dAtA, i, uint64(size)) + dAtA[i] = 0x1a + n2, err2 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(m.LastReleaseTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(m.LastReleaseTime):]) + if err2 != nil { + return 0, err2 } + i -= n2 + i = encodeVarintTypes(dAtA, i, uint64(n2)) i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *RewardPool) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *RewardPool) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *RewardPool) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l + dAtA[i] = 0x12 if len(m.CommunityPool) > 0 { for iNdEx := len(m.CommunityPool) - 1; iNdEx >= 0; iNdEx-- { { @@ -318,26 +196,6 @@ func encodeVarintTypes(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return base } -func (m *ReleaseSchedule) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.TotalAmount.Size() - n += 1 + l + sovTypes(uint64(l)) - l = m.ReleasedAmount.Size() - n += 1 + l + sovTypes(uint64(l)) - l = github_com_cosmos_gogoproto_types.SizeOfStdTime(m.EndTime) - n += 1 + l + sovTypes(uint64(l)) - l = github_com_cosmos_gogoproto_types.SizeOfStdTime(m.LastReleaseTime) - n += 1 + l + sovTypes(uint64(l)) - if m.Active { - n += 2 - } - return n -} - func (m *RewardPool) Size() (n int) { if m == nil { return 0 @@ -350,6 +208,10 @@ func (m *RewardPool) Size() (n int) { n += 1 + l + sovTypes(uint64(l)) } } + l = github_com_cosmos_gogoproto_types.SizeOfStdTime(m.LastReleaseTime) + n += 1 + l + sovTypes(uint64(l)) + l = m.TotalReleased.Size() + n += 1 + l + sovTypes(uint64(l)) return n } @@ -359,7 +221,7 @@ func sovTypes(x uint64) (n int) { func sozTypes(x uint64) (n int) { return sovTypes(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *ReleaseSchedule) Unmarshal(dAtA []byte) error { +func (m *RewardPool) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -382,15 +244,15 @@ func (m *ReleaseSchedule) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: ReleaseSchedule: wiretype end group for non-group") + return fmt.Errorf("proto: RewardPool: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: ReleaseSchedule: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: RewardPool: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field TotalAmount", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CommunityPool", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -417,77 +279,12 @@ func (m *ReleaseSchedule) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.TotalAmount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.CommunityPool = append(m.CommunityPool, types.DecCoin{}) + if err := m.CommunityPool[len(m.CommunityPool)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ReleasedAmount", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.ReleasedAmount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field EndTime", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := github_com_cosmos_gogoproto_types.StdTimeUnmarshal(&m.EndTime, dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field LastReleaseTime", wireType) } @@ -520,79 +317,9 @@ func (m *ReleaseSchedule) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 6: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Active", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.Active = bool(v != 0) - default: - iNdEx = preIndex - skippy, err := skipTypes(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTypes - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *RewardPool) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: RewardPool: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: RewardPool: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CommunityPool", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field TotalReleased", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -619,8 +346,7 @@ func (m *RewardPool) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.CommunityPool = append(m.CommunityPool, types.DecCoin{}) - if err := m.CommunityPool[len(m.CommunityPool)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.TotalReleased.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex diff --git a/x/tokenfactory/module.go b/x/tokenfactory/module.go index d4d20030..75f0763b 100644 --- a/x/tokenfactory/module.go +++ b/x/tokenfactory/module.go @@ -19,6 +19,8 @@ import ( abci "github.com/cometbft/cometbft/abci/types" + "cosmossdk.io/core/appmodule" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" cdctypes "github.com/cosmos/cosmos-sdk/codec/types" @@ -35,8 +37,8 @@ import ( ) var ( - _ module.AppModule = AppModule{} //nolint:staticcheck _ module.AppModuleBasic = AppModuleBasic{} + _ appmodule.AppModule = AppModule{} ) // ConsensusVersion defines the current x/tokenfactory module consensus version.