diff --git a/docs/api.md b/docs/api.md index 24cd10fb..370ba537 100644 --- a/docs/api.md +++ b/docs/api.md @@ -311,6 +311,7 @@ - [MsgUpdateClearingAccountMappings](#tx.pse.v1.MsgUpdateClearingAccountMappings) - [MsgUpdateDistributionSchedule](#tx.pse.v1.MsgUpdateDistributionSchedule) - [MsgUpdateExcludedAddresses](#tx.pse.v1.MsgUpdateExcludedAddresses) + - [MsgUpdateMinDistributionGap](#tx.pse.v1.MsgUpdateMinDistributionGap) - [Msg](#tx.pse.v1.Msg) @@ -5818,6 +5819,7 @@ During distribution, the allocated amount is split equally among all recipients. ``` ScheduledDistribution defines a single allocation event at a specific timestamp. Multiple clearing accounts can allocate tokens at the same time. +Each distribution is identified by a unique, sequential id. ``` @@ -5826,6 +5828,7 @@ Multiple clearing accounts can allocate tokens at the same time. | ----- | ---- | ----- | ----------- | | `timestamp` | [uint64](#uint64) | | `timestamp is when this allocation should occur (Unix timestamp in seconds).` | | `allocations` | [ClearingAccountAllocation](#tx.pse.v1.ClearingAccountAllocation) | repeated | `allocations is the list of amounts to allocate from each clearing account at this time.` | +| `id` | [uint64](#uint64) | | `id is the unique, sequential identifier for this distribution. Used as the storage key in the AllocationSchedule map.` | @@ -5996,6 +5999,7 @@ Params store gov manageable parameters. | ----- | ---- | ----- | ----------- | | `excluded_addresses` | [string](#string) | repeated | `excluded_addresses is a list of addresses excluded from PSE distribution. This list includes account addresses that should not receive PSE rewards. Can be modified via governance proposals.` | | `clearing_account_mappings` | [ClearingAccountMapping](#tx.pse.v1.ClearingAccountMapping) | repeated | `clearing_account_mappings defines the mapping between clearing accounts and their sub accounts (multisig wallets). These mappings can be modified via governance proposals.` | +| `min_distribution_gap_seconds` | [uint64](#uint64) | | `min_distribution_gap_seconds is the minimum required gap in seconds between consecutive distributions.` | @@ -6327,6 +6331,28 @@ All existing distributions are removed and replaced with the provided distributi + + + +### MsgUpdateMinDistributionGap + +``` +MsgUpdateMinDistributionGap is a governance operation to update the minimum time gap +between consecutive scheduled distributions. The new gap is validated against the +existing on-chain schedule to ensure consistency. +``` + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `authority` | [string](#string) | | `authority is the address authorized to update the gap (governance module address).` | +| `min_distribution_gap_seconds` | [uint64](#uint64) | | `min_distribution_gap_seconds is the minimum time gap (in seconds) between consecutive distributions.` | + + + + + @@ -6349,6 +6375,7 @@ Msg defines the Msg service. | `UpdateClearingAccountMappings` | [MsgUpdateClearingAccountMappings](#tx.pse.v1.MsgUpdateClearingAccountMappings) | [EmptyResponse](#tx.pse.v1.EmptyResponse) | `UpdateClearingAccountMappings is a governance operation to update clearing account to recipient mappings.` | | | `UpdateDistributionSchedule` | [MsgUpdateDistributionSchedule](#tx.pse.v1.MsgUpdateDistributionSchedule) | [EmptyResponse](#tx.pse.v1.EmptyResponse) | `UpdateDistributionSchedule is a governance operation to update the distribution schedule.` | | | `DisableDistributions` | [MsgDisableDistributions](#tx.pse.v1.MsgDisableDistributions) | [EmptyResponse](#tx.pse.v1.EmptyResponse) | `DisableDistributions is a governance operation to disable distributions.` | | +| `UpdateMinDistributionGap` | [MsgUpdateMinDistributionGap](#tx.pse.v1.MsgUpdateMinDistributionGap) | [EmptyResponse](#tx.pse.v1.EmptyResponse) | `UpdateMinDistributionGap is a governance operation to update the minimum gap between distributions.` | | diff --git a/docs/static/openapi.json b/docs/static/openapi.json index 31af7372..301d272d 100644 --- a/docs/static/openapi.json +++ b/docs/static/openapi.json @@ -16804,6 +16804,11 @@ "$ref": "#/definitions/tx.pse.v1.ClearingAccountMapping" }, "description": "clearing_account_mappings defines the mapping between clearing accounts and their sub accounts (multisig wallets).\nThese mappings can be modified via governance proposals." + }, + "min_distribution_gap_seconds": { + "type": "string", + "format": "uint64", + "description": "min_distribution_gap_seconds is the minimum required gap in seconds between consecutive distributions." } }, "description": "Params store gov manageable parameters." @@ -16874,9 +16879,14 @@ "$ref": "#/definitions/tx.pse.v1.ClearingAccountAllocation" }, "description": "allocations is the list of amounts to allocate from each clearing account at this time." + }, + "id": { + "type": "string", + "format": "uint64", + "description": "id is the unique, sequential identifier for this distribution.\nUsed as the storage key in the AllocationSchedule map." } }, - "description": "ScheduledDistribution defines a single allocation event at a specific timestamp.\nMultiple clearing accounts can allocate tokens at the same time." + "description": "ScheduledDistribution defines a single allocation event at a specific timestamp.\nMultiple clearing accounts can allocate tokens at the same time.\nEach distribution is identified by a unique, sequential id." } } } diff --git a/integration-tests/modules/pse_test.go b/integration-tests/modules/pse_test.go index 10887ce1..16c7e127 100644 --- a/integration-tests/modules/pse_test.go +++ b/integration-tests/modules/pse_test.go @@ -210,12 +210,16 @@ func TestPSEDistribution(t *testing.T) { chain.Governance.ExpeditedProposalFromMsgAndVote( ctx, t, nil, "-", "-", "-", govtypesv1.OptionYes, + &psetypes.MsgUpdateMinDistributionGap{ + Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), + MinDistributionGapSeconds: 0, + }, &psetypes.MsgUpdateDistributionSchedule{ Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), Schedule: []psetypes.ScheduledDistribution{ - {Timestamp: uint64(distributionStartTime.Add(30 * time.Second).Unix()), Allocations: allocations}, - {Timestamp: uint64(distributionStartTime.Add(60 * time.Second).Unix()), Allocations: allocations}, - {Timestamp: uint64(distributionStartTime.Add(90 * time.Second).Unix()), Allocations: allocations}, + {ID: 1, Timestamp: uint64(distributionStartTime.Add(30 * time.Second).Unix()), Allocations: allocations}, + {ID: 2, Timestamp: uint64(distributionStartTime.Add(60 * time.Second).Unix()), Allocations: allocations}, + {ID: 3, Timestamp: uint64(distributionStartTime.Add(90 * time.Second).Unix()), Allocations: allocations}, }, }, &psetypes.MsgUpdateClearingAccountMappings{ diff --git a/proto/tx/pse/v1/distribution.proto b/proto/tx/pse/v1/distribution.proto index f2769a4a..1891f271 100644 --- a/proto/tx/pse/v1/distribution.proto +++ b/proto/tx/pse/v1/distribution.proto @@ -43,6 +43,7 @@ message ClearingAccountAllocation { // ScheduledDistribution defines a single allocation event at a specific timestamp. // Multiple clearing accounts can allocate tokens at the same time. +// Each distribution is identified by a unique, sequential id. message ScheduledDistribution { // timestamp is when this allocation should occur (Unix timestamp in seconds). uint64 timestamp = 1 [ @@ -54,5 +55,12 @@ message ScheduledDistribution { (gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"allocations\"" ]; + + // id is the unique, sequential identifier for this distribution. + // Used as the storage key in the AllocationSchedule map. + uint64 id = 3 [ + (gogoproto.customname) = "ID", + (gogoproto.moretags) = "yaml:\"id\"" + ]; } diff --git a/proto/tx/pse/v1/params.proto b/proto/tx/pse/v1/params.proto index 471ee613..d1e22369 100644 --- a/proto/tx/pse/v1/params.proto +++ b/proto/tx/pse/v1/params.proto @@ -23,4 +23,9 @@ message Params { (gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"clearing_account_mappings\"" ]; + + // min_distribution_gap_seconds is the minimum required gap in seconds between consecutive distributions. + uint64 min_distribution_gap_seconds = 3 [ + (gogoproto.moretags) = "yaml:\"min_distribution_gap_seconds\"" + ]; } diff --git a/proto/tx/pse/v1/tx.proto b/proto/tx/pse/v1/tx.proto index 42311ab2..e1a0d8dc 100644 --- a/proto/tx/pse/v1/tx.proto +++ b/proto/tx/pse/v1/tx.proto @@ -24,6 +24,9 @@ service Msg { // DisableDistributions is a governance operation to disable distributions. rpc DisableDistributions(MsgDisableDistributions) returns (EmptyResponse); + + // UpdateMinDistributionGap is a governance operation to update the minimum gap between distributions. + rpc UpdateMinDistributionGap(MsgUpdateMinDistributionGap) returns (EmptyResponse); } message MsgDisableDistributions { @@ -90,4 +93,20 @@ message MsgUpdateDistributionSchedule { ]; } +// MsgUpdateMinDistributionGap is a governance operation to update the minimum time gap +// between consecutive scheduled distributions. The new gap is validated against the +// existing on-chain schedule to ensure consistency. +message MsgUpdateMinDistributionGap { + option (cosmos.msg.v1.signer) = "authority"; + option (amino.name) = "pse/MsgUpdateMinDistributionGap"; + + // authority is the address authorized to update the gap (governance module address). + string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + + // min_distribution_gap_seconds is the minimum time gap (in seconds) between consecutive distributions. + uint64 min_distribution_gap_seconds = 2 [ + (gogoproto.moretags) = "yaml:\"min_distribution_gap_seconds\"" + ]; +} + message EmptyResponse {} diff --git a/x/deterministicgas/config.go b/x/deterministicgas/config.go index dde1600a..d2f882cc 100644 --- a/x/deterministicgas/config.go +++ b/x/deterministicgas/config.go @@ -239,6 +239,7 @@ func DefaultConfig() Config { &psetypes.MsgUpdateClearingAccountMappings{}, &psetypes.MsgUpdateDistributionSchedule{}, &psetypes.MsgDisableDistributions{}, + &psetypes.MsgUpdateMinDistributionGap{}, // distribution &distributiontypes.MsgUpdateParams{}, // This is non-deterministic because all the gov proposals are non-deterministic anyway diff --git a/x/deterministicgas/config_test.go b/x/deterministicgas/config_test.go index 78b4d18b..80083ea9 100644 --- a/x/deterministicgas/config_test.go +++ b/x/deterministicgas/config_test.go @@ -97,10 +97,10 @@ func TestDeterministicGas_DeterministicMessages(t *testing.T) { // To make sure we do not increase/decrease deterministic and extension types accidentally, // we assert length to be equal to exact number, so each change requires // explicit adjustment of tests. - assert.Equal(t, 94, nondeterministicMsgCount) + assert.Equal(t, 95, nondeterministicMsgCount) assert.Equal(t, 68, deterministicMsgCount) assert.Equal(t, 12, extensionMsgCount) - assert.Equal(t, 150, nonExtensionMsgCount) + assert.Equal(t, 151, nonExtensionMsgCount) } func TestDeterministicGas_GasRequiredByMessage(t *testing.T) { diff --git a/x/deterministicgas/spec/README.md b/x/deterministicgas/spec/README.md index d871d51b..a0ff98f0 100644 --- a/x/deterministicgas/spec/README.md +++ b/x/deterministicgas/spec/README.md @@ -306,6 +306,7 @@ and the formula for them is | `/tx.pse.v1.MsgUpdateClearingAccountMappings` | | `/tx.pse.v1.MsgUpdateDistributionSchedule` | | `/tx.pse.v1.MsgUpdateExcludedAddresses` | +| `/tx.pse.v1.MsgUpdateMinDistributionGap` | [//]: # (GENERATED DOC.) [//]: # (DO NOT EDIT MANUALLY!!!) diff --git a/x/pse/keeper/distribution.go b/x/pse/keeper/distribution.go index 350cc63b..1bad66b9 100644 --- a/x/pse/keeper/distribution.go +++ b/x/pse/keeper/distribution.go @@ -50,7 +50,7 @@ func (k Keeper) ProcessNextDistribution(ctx context.Context) error { } // Remove the completed distribution from the schedule - if err := k.AllocationSchedule.Remove(ctx, timestamp); err != nil { + if err := k.AllocationSchedule.Remove(ctx, scheduledDistribution.ID); err != nil { return err } @@ -64,7 +64,7 @@ func (k Keeper) ProcessNextDistribution(ctx context.Context) error { func (k Keeper) PeekNextAllocationSchedule(ctx context.Context) (types.ScheduledDistribution, bool, error) { sdkCtx := sdk.UnwrapSDKContext(ctx) - // Get iterator for the allocation schedule (sorted by timestamp ascending) + // Get iterator for the allocation schedule (sorted by id ascending) iter, err := k.AllocationSchedule.Iterate(ctx, nil) if err != nil { return types.ScheduledDistribution{}, false, err @@ -76,18 +76,18 @@ func (k Keeper) PeekNextAllocationSchedule(ctx context.Context) (types.Scheduled return types.ScheduledDistribution{}, false, nil } - // Extract the earliest scheduled distribution + // Extract the earliest scheduled distribution (sorted by id ascending) kv, err := iter.KeyValue() if err != nil { return types.ScheduledDistribution{}, false, err } - timestamp := kv.Key scheduledDist := kv.Value // Check if distribution time has arrived - // Since the map is sorted by timestamp, if the first item is in the future, all items are - shouldProcess := timestamp <= uint64(sdkCtx.BlockTime().Unix()) + // Since IDs are sequential and timestamps are monotonically increasing, + // the first item by ID is also the earliest by time. + shouldProcess := scheduledDist.Timestamp <= uint64(sdkCtx.BlockTime().Unix()) return scheduledDist, shouldProcess, nil } @@ -217,18 +217,18 @@ func (k Keeper) distributeAllocatedTokens( } // SaveDistributionSchedule persists the distribution schedule to blockchain state. -// Each scheduled distribution is stored in the AllocationSchedule map, indexed by its timestamp. +// Each scheduled distribution is stored in the AllocationSchedule map, indexed by its ID. func (k Keeper) SaveDistributionSchedule(ctx context.Context, schedule []types.ScheduledDistribution) error { for _, scheduledDist := range schedule { - if err := k.AllocationSchedule.Set(ctx, scheduledDist.Timestamp, scheduledDist); err != nil { - return errorsmod.Wrapf(err, "failed to save distribution at timestamp %d", scheduledDist.Timestamp) + if err := k.AllocationSchedule.Set(ctx, scheduledDist.ID, scheduledDist); err != nil { + return errorsmod.Wrapf(err, "failed to save distribution with id %d", scheduledDist.ID) } } return nil } // GetDistributionSchedule returns the complete allocation schedule as a sorted list. -// The schedule is sorted by timestamp in ascending order. +// The schedule is sorted by id in ascending order. // Returns an empty slice if no allocations are scheduled. // Note: Past schedule allocations removed after processing, so this only contains future schedule allocations. func (k Keeper) GetDistributionSchedule(ctx context.Context) ([]types.ScheduledDistribution, error) { @@ -248,7 +248,7 @@ func (k Keeper) GetDistributionSchedule(ctx context.Context) ([]types.ScheduledD schedule = append(schedule, kv.Value) } - // Note: Collections map iterates in ascending order of keys (timestamps), + // Note: Collections map iterates in ascending order of keys (IDs), // so the schedule is already sorted. No need to sort again. return schedule, nil } @@ -266,6 +266,15 @@ func (k Keeper) UpdateDistributionSchedule( return errorsmod.Wrapf(types.ErrInvalidAuthority, "expected %s, got %s", k.authority, authority) } + // Validate minimum gap between distributions + params, err := k.GetParams(ctx) + if err != nil { + return err + } + if err := types.ValidateDistributionGap(newSchedule, params.MinDistributionGapSeconds); err != nil { + return err + } + // Clear all existing schedule entries if err := k.AllocationSchedule.Clear(ctx, nil); err != nil { return errorsmod.Wrap(err, "failed to clear existing allocation schedule") @@ -275,6 +284,35 @@ func (k Keeper) UpdateDistributionSchedule( return k.SaveDistributionSchedule(ctx, newSchedule) } +// UpdateMinDistributionGap updates the minimum time gap between distributions via governance. +// The new gap is validated against the existing on-chain schedule to ensure consistency. +func (k Keeper) UpdateMinDistributionGap( + ctx context.Context, + authority string, + minGapSeconds uint64, +) error { + if k.authority != authority { + return errorsmod.Wrapf(types.ErrInvalidAuthority, "expected %s, got %s", k.authority, authority) + } + + // Validate new gap against existing schedule + schedule, err := k.GetDistributionSchedule(ctx) + if err != nil { + return err + } + if err := types.ValidateDistributionGap(schedule, minGapSeconds); err != nil { + return errorsmod.Wrapf(err, "existing schedule violates proposed min gap of %d seconds", minGapSeconds) + } + + // Update params + params, err := k.GetParams(ctx) + if err != nil { + return err + } + params.MinDistributionGapSeconds = minGapSeconds + return k.SetParams(ctx, params) +} + // DisableDistributions is a governance operation that disables distributions. func (k Keeper) DisableDistributions(ctx context.Context, authority string) error { // Check authority diff --git a/x/pse/keeper/distribution_test.go b/x/pse/keeper/distribution_test.go index 17ffca1b..fa386454 100644 --- a/x/pse/keeper/distribution_test.go +++ b/x/pse/keeper/distribution_test.go @@ -63,6 +63,7 @@ func TestDistribution_GenesisRebuild(t *testing.T) { // Create and store allocation schedule with all clearing accounts schedule := []types.ScheduledDistribution{ { + ID: 1, Timestamp: time1, Allocations: []types.ClearingAccountAllocation{ {ClearingAccount: types.ClearingAccountCommunity, Amount: sdkmath.NewInt(5000)}, @@ -74,6 +75,7 @@ func TestDistribution_GenesisRebuild(t *testing.T) { }, }, { + ID: 2, Timestamp: time2, Allocations: []types.ClearingAccountAllocation{ {ClearingAccount: types.ClearingAccountCommunity, Amount: sdkmath.NewInt(10000)}, @@ -88,7 +90,7 @@ func TestDistribution_GenesisRebuild(t *testing.T) { // Store in allocation schedule map for _, scheduledDist := range schedule { - err = pseKeeper.AllocationSchedule.Set(ctx, scheduledDist.Timestamp, scheduledDist) + err = pseKeeper.AllocationSchedule.Set(ctx, scheduledDist.ID, scheduledDist) requireT.NoError(err) } @@ -183,6 +185,7 @@ func TestDistribution_PrecisionWithMultipleRecipients(t *testing.T) { startTime := uint64(time.Now().Add(-1 * time.Hour).Unix()) schedule := []types.ScheduledDistribution{ { + ID: 1, Timestamp: startTime, Allocations: []types.ClearingAccountAllocation{ {ClearingAccount: types.ClearingAccountFoundation, Amount: allocationAmount}, @@ -314,6 +317,7 @@ func TestDistribution_EndBlockFailure(t *testing.T) { startTime := uint64(time.Now().Add(-1 * time.Hour).Unix()) schedule := []types.ScheduledDistribution{ { + ID: 1, Timestamp: startTime, Allocations: []types.ClearingAccountAllocation{ {ClearingAccount: types.ClearingAccountFoundation, Amount: allocationAmount}, diff --git a/x/pse/keeper/genesis.go b/x/pse/keeper/genesis.go index 977f7336..7e479440 100644 --- a/x/pse/keeper/genesis.go +++ b/x/pse/keeper/genesis.go @@ -23,7 +23,7 @@ func (k Keeper) InitGenesis(ctx context.Context, genState types.GenesisState) er // Populate allocation schedule from genesis state for _, scheduledDist := range genState.ScheduledDistributions { - if err := k.AllocationSchedule.Set(ctx, scheduledDist.Timestamp, scheduledDist); err != nil { + if err := k.AllocationSchedule.Set(ctx, scheduledDist.ID, scheduledDist); err != nil { return err } } @@ -70,7 +70,7 @@ func (k Keeper) ExportGenesis(ctx context.Context) (*types.GenesisState, error) return nil, err } - // Export allocation schedule using keeper method (already sorted by timestamp) + // Export allocation schedule using keeper method (already sorted by id) genesis.ScheduledDistributions, err = k.GetDistributionSchedule(ctx) if err != nil { return nil, err diff --git a/x/pse/keeper/genesis_test.go b/x/pse/keeper/genesis_test.go index ff8593e0..287a54c8 100644 --- a/x/pse/keeper/genesis_test.go +++ b/x/pse/keeper/genesis_test.go @@ -136,6 +136,7 @@ func TestGenesis_InvalidState(t *testing.T) { // Only include 4 accounts, missing Community and Team gs.ScheduledDistributions = []types.ScheduledDistribution{ { + ID: 1, Timestamp: now, Allocations: []types.ClearingAccountAllocation{ {ClearingAccount: types.ClearingAccountFoundation, Amount: sdkmath.NewInt(1000)}, @@ -155,6 +156,7 @@ func TestGenesis_InvalidState(t *testing.T) { // Include only non-Community accounts, missing Community gs.ScheduledDistributions = []types.ScheduledDistribution{ { + ID: 1, Timestamp: now, Allocations: []types.ClearingAccountAllocation{ {ClearingAccount: types.ClearingAccountFoundation, Amount: sdkmath.NewInt(1000)}, diff --git a/x/pse/keeper/grpc_query_test.go b/x/pse/keeper/grpc_query_test.go index 42111114..01ae681d 100644 --- a/x/pse/keeper/grpc_query_test.go +++ b/x/pse/keeper/grpc_query_test.go @@ -285,12 +285,14 @@ func TestQueryAllocationSchedule(t *testing.T) { // Create schedule allocations at different future times schedule1 := types.ScheduledDistribution{ + ID: 1, Timestamp: uint64(currentTime.Add(1 * time.Hour).Unix()), Allocations: []types.ClearingAccountAllocation{ {ClearingAccount: types.ClearingAccountFoundation, Amount: sdkmath.NewInt(2000)}, }, } schedule2 := types.ScheduledDistribution{ + ID: 2, Timestamp: uint64(currentTime.Add(2 * time.Hour).Unix()), Allocations: []types.ClearingAccountAllocation{ {ClearingAccount: types.ClearingAccountTeam, Amount: sdkmath.NewInt(3000)}, @@ -315,6 +317,7 @@ func TestQueryAllocationSchedule(t *testing.T) { queryService := keeper.NewQueryService(testApp.PSEKeeper) schedule := types.ScheduledDistribution{ + ID: 1, Timestamp: uint64(currentTime.Add(1 * time.Hour).Unix()), Allocations: []types.ClearingAccountAllocation{ {ClearingAccount: types.ClearingAccountCommunity, Amount: sdkmath.NewInt(1000)}, @@ -332,27 +335,30 @@ func TestQueryAllocationSchedule(t *testing.T) { requireT.Len(resp.ScheduledDistributions[0].Allocations, 3) }) - t.Run("schedule sorted by timestamp", func(t *testing.T) { + t.Run("schedule sorted by id", func(t *testing.T) { requireT := require.New(t) testApp := simapp.New() currentTime := time.Now() ctx := testApp.NewContext(false).WithBlockTime(currentTime) queryService := keeper.NewQueryService(testApp.PSEKeeper) - // Save schedule item in non-chronological order + // Save schedule items in non-sequential order of id schedule3 := types.ScheduledDistribution{ + ID: 3, Timestamp: uint64(currentTime.Add(3 * time.Hour).Unix()), Allocations: []types.ClearingAccountAllocation{ {ClearingAccount: types.ClearingAccountAlliance, Amount: sdkmath.NewInt(3000)}, }, } schedule1 := types.ScheduledDistribution{ + ID: 1, Timestamp: uint64(currentTime.Add(1 * time.Hour).Unix()), Allocations: []types.ClearingAccountAllocation{ {ClearingAccount: types.ClearingAccountPartnership, Amount: sdkmath.NewInt(1000)}, }, } schedule2 := types.ScheduledDistribution{ + ID: 2, Timestamp: uint64(currentTime.Add(2 * time.Hour).Unix()), Allocations: []types.ClearingAccountAllocation{ {ClearingAccount: types.ClearingAccountInvestors, Amount: sdkmath.NewInt(2000)}, @@ -365,7 +371,7 @@ func TestQueryAllocationSchedule(t *testing.T) { resp, err := queryService.ScheduledDistributions(ctx, &types.QueryScheduledDistributionsRequest{}) requireT.NoError(err) requireT.Len(resp.ScheduledDistributions, 3) - // Verify schedule are sorted by timestamp in ascending order + // Verify schedule items are sorted by id in ascending order requireT.Equal(schedule1.Timestamp, resp.ScheduledDistributions[0].Timestamp) requireT.Equal(schedule2.Timestamp, resp.ScheduledDistributions[1].Timestamp) requireT.Equal(schedule3.Timestamp, resp.ScheduledDistributions[2].Timestamp) diff --git a/x/pse/keeper/keeper.go b/x/pse/keeper/keeper.go index eb075c32..a572aed9 100644 --- a/x/pse/keeper/keeper.go +++ b/x/pse/keeper/keeper.go @@ -34,7 +34,7 @@ type Keeper struct { Params collections.Item[types.Params] DelegationTimeEntries collections.Map[collections.Pair[sdk.AccAddress, sdk.ValAddress], types.DelegationTimeEntry] AccountScoreSnapshot collections.Map[sdk.AccAddress, sdkmath.Int] - AllocationSchedule collections.Map[uint64, types.ScheduledDistribution] // Map: timestamp -> ScheduledDistribution + AllocationSchedule collections.Map[uint64, types.ScheduledDistribution] // Map: id -> ScheduledDistribution DistributionDisabled collections.Item[bool] } diff --git a/x/pse/keeper/msg_server.go b/x/pse/keeper/msg_server.go index 16052c02..11a63f67 100644 --- a/x/pse/keeper/msg_server.go +++ b/x/pse/keeper/msg_server.go @@ -69,6 +69,22 @@ func (ms MsgServer) UpdateDistributionSchedule( return &types.EmptyResponse{}, nil } +// UpdateMinDistributionGap is a governance operation that updates the minimum distribution gap. +func (ms MsgServer) UpdateMinDistributionGap( + goCtx context.Context, + req *types.MsgUpdateMinDistributionGap, +) (*types.EmptyResponse, error) { + err := ms.keeper.UpdateMinDistributionGap( + goCtx, + req.Authority, + req.MinDistributionGapSeconds, + ) + if err != nil { + return nil, err + } + return &types.EmptyResponse{}, nil +} + // DisableDistributions is a governance operation that disables distributions. func (ms MsgServer) DisableDistributions( goCtx context.Context, diff --git a/x/pse/keeper/msg_server_test.go b/x/pse/keeper/msg_server_test.go index b0f18941..6915a54b 100644 --- a/x/pse/keeper/msg_server_test.go +++ b/x/pse/keeper/msg_server_test.go @@ -182,6 +182,7 @@ func TestMsgUpdateAllocationSchedule(t *testing.T) { baseTimestamp := uint64(1700000000) // Some future timestamp for i := range numPeriods { schedule[i] = types.ScheduledDistribution{ + ID: uint64(i + 1), Timestamp: baseTimestamp + uint64(i*86400), // One day apart Allocations: createAllAllocations(amount), } @@ -225,6 +226,7 @@ func TestMsgUpdateAllocationSchedule(t *testing.T) { Authority: authority, Schedule: []types.ScheduledDistribution{ { + ID: 1, Timestamp: uint64(1700000000), Allocations: []types.ClearingAccountAllocation{ {ClearingAccount: types.ClearingAccountCommunity, Amount: sdkmath.NewInt(1000000)}, @@ -297,3 +299,112 @@ func TestMsgUpdateAllocationSchedule(t *testing.T) { }) } } + +func TestMsgUpdateMinDistributionGap(t *testing.T) { + requireT := require.New(t) + + testApp := simapp.New() + ctx := testApp.NewContext(false) + msgServer := keeper.NewMsgServer(testApp.PSEKeeper) + + authority := authtypes.NewModuleAddress(govtypes.ModuleName).String() + invalidAuthority := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()).String() + + // Set up a schedule with 1-day gaps + baseTimestamp := uint64(1700000000) + daySeconds := uint64(86400) + schedule := []types.ScheduledDistribution{ + { + ID: 1, + Timestamp: baseTimestamp, + Allocations: []types.ClearingAccountAllocation{ + {ClearingAccount: types.ClearingAccountCommunity, Amount: sdkmath.NewInt(1000)}, + {ClearingAccount: types.ClearingAccountFoundation, Amount: sdkmath.NewInt(1000)}, + {ClearingAccount: types.ClearingAccountAlliance, Amount: sdkmath.NewInt(1000)}, + {ClearingAccount: types.ClearingAccountPartnership, Amount: sdkmath.NewInt(1000)}, + {ClearingAccount: types.ClearingAccountInvestors, Amount: sdkmath.NewInt(1000)}, + {ClearingAccount: types.ClearingAccountTeam, Amount: sdkmath.NewInt(1000)}, + }, + }, + { + ID: 2, + Timestamp: baseTimestamp + daySeconds, // 1 day later + Allocations: []types.ClearingAccountAllocation{ + {ClearingAccount: types.ClearingAccountCommunity, Amount: sdkmath.NewInt(1000)}, + {ClearingAccount: types.ClearingAccountFoundation, Amount: sdkmath.NewInt(1000)}, + {ClearingAccount: types.ClearingAccountAlliance, Amount: sdkmath.NewInt(1000)}, + {ClearingAccount: types.ClearingAccountPartnership, Amount: sdkmath.NewInt(1000)}, + {ClearingAccount: types.ClearingAccountInvestors, Amount: sdkmath.NewInt(1000)}, + {ClearingAccount: types.ClearingAccountTeam, Amount: sdkmath.NewInt(1000)}, + }, + }, + } + + // Save schedule directly + err := testApp.PSEKeeper.SaveDistributionSchedule(ctx, schedule) + requireT.NoError(err) + + t.Run("valid - set gap less than existing interval", func(t *testing.T) { + resp, err := msgServer.UpdateMinDistributionGap(ctx, &types.MsgUpdateMinDistributionGap{ + Authority: authority, + MinDistributionGapSeconds: daySeconds / 2, // 12 hours, schedule has 1-day gaps + }) + requireT.NoError(err) + requireT.NotNil(resp) + + // Verify param was updated + params, err := testApp.PSEKeeper.GetParams(ctx) + requireT.NoError(err) + requireT.Equal(daySeconds/2, params.MinDistributionGapSeconds) + }) + + t.Run("valid - set gap equal to existing interval", func(t *testing.T) { + resp, err := msgServer.UpdateMinDistributionGap(ctx, &types.MsgUpdateMinDistributionGap{ + Authority: authority, + MinDistributionGapSeconds: daySeconds, // exactly 1 day (matches schedule) + }) + requireT.NoError(err) + requireT.NotNil(resp) + + params, err := testApp.PSEKeeper.GetParams(ctx) + requireT.NoError(err) + requireT.Equal(daySeconds, params.MinDistributionGapSeconds) + }) + + t.Run("invalid - gap larger than existing schedule interval", func(t *testing.T) { + resp, err := msgServer.UpdateMinDistributionGap(ctx, &types.MsgUpdateMinDistributionGap{ + Authority: authority, + MinDistributionGapSeconds: daySeconds * 2, // 2 days, but schedule has 1-day gaps + }) + requireT.Error(err) + requireT.Nil(resp) + requireT.Contains(err.Error(), "existing schedule violates proposed min gap") + }) + + t.Run("invalid - wrong authority", func(t *testing.T) { + resp, err := msgServer.UpdateMinDistributionGap(ctx, &types.MsgUpdateMinDistributionGap{ + Authority: invalidAuthority, + MinDistributionGapSeconds: daySeconds / 2, + }) + requireT.Error(err) + requireT.Nil(resp) + requireT.Contains(err.Error(), "invalid authority") + }) + + t.Run("valid - zero gap with empty schedule", func(t *testing.T) { + // Clear schedule + err := testApp.PSEKeeper.AllocationSchedule.Clear(ctx, nil) + requireT.NoError(err) + + resp, err := msgServer.UpdateMinDistributionGap(ctx, &types.MsgUpdateMinDistributionGap{ + Authority: authority, + MinDistributionGapSeconds: 0, + }) + requireT.NoError(err) + requireT.NotNil(resp) + + params, err := testApp.PSEKeeper.GetParams(ctx) + requireT.NoError(err) + requireT.Equal(uint64(0), params.MinDistributionGapSeconds) + }) +} diff --git a/x/pse/spec/README.md b/x/pse/spec/README.md index 54e92bee..a255ec46 100644 --- a/x/pse/spec/README.md +++ b/x/pse/spec/README.md @@ -126,7 +126,7 @@ State managed by the PSE module: - **Params**: `0x00 | -> Params` - **DelegationTimeEntries**: `0x01 | delegator_address | validator_address -> DelegationTimeEntry` - **AccountScoreSnapshot**: `0x02 | delegator_address -> Int` -- **AllocationSchedule**: `0x03 | timestamp (uint64) -> ScheduledDistribution` +- **AllocationSchedule**: `0x03 | id (uint64) -> ScheduledDistribution` ### Params @@ -134,6 +134,7 @@ Module parameters containing: - `ExcludedAddresses`: List of addresses excluded from Community distributions - `ClearingAccountMappings`: Recipient address mappings for non-Community clearing accounts +- `MinDistributionGapSeconds`: Minimum time gap (in seconds) between consecutive scheduled distributions (default: 86400 = 1 day) ### DelegationTimeEntry @@ -155,12 +156,13 @@ Stores the accumulated score for each delegator address over the current 1-month ### AllocationSchedule -Maps timestamps to scheduled distributions: +Maps distribution IDs to scheduled distributions: ```protobuf message ScheduledDistribution { uint64 timestamp = 1; // Unix timestamp when distribution should occur repeated ClearingAccountAllocation allocations = 2; // Allocations for each clearing account + uint64 id = 3; // Unique, sequential identifier (storage key) } message ClearingAccountAllocation { diff --git a/x/pse/types/distribution.pb.go b/x/pse/types/distribution.pb.go index 53de4c0f..4010e758 100644 --- a/x/pse/types/distribution.pb.go +++ b/x/pse/types/distribution.pb.go @@ -135,11 +135,15 @@ func (m *ClearingAccountAllocation) GetClearingAccount() string { // ScheduledDistribution defines a single allocation event at a specific timestamp. // Multiple clearing accounts can allocate tokens at the same time. +// Each distribution is identified by a unique, sequential id. type ScheduledDistribution struct { // timestamp is when this allocation should occur (Unix timestamp in seconds). Timestamp uint64 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty" yaml:"timestamp"` // allocations is the list of amounts to allocate from each clearing account at this time. Allocations []ClearingAccountAllocation `protobuf:"bytes,2,rep,name=allocations,proto3" json:"allocations" yaml:"allocations"` + // id is the unique, sequential identifier for this distribution. + // Used as the storage key in the AllocationSchedule map. + ID uint64 `protobuf:"varint,3,opt,name=id,proto3" json:"id,omitempty" yaml:"id"` } func (m *ScheduledDistribution) Reset() { *m = ScheduledDistribution{} } @@ -189,6 +193,13 @@ func (m *ScheduledDistribution) GetAllocations() []ClearingAccountAllocation { return nil } +func (m *ScheduledDistribution) GetID() uint64 { + if m != nil { + return m.ID + } + return 0 +} + func init() { proto.RegisterType((*ClearingAccountMapping)(nil), "tx.pse.v1.ClearingAccountMapping") proto.RegisterType((*ClearingAccountAllocation)(nil), "tx.pse.v1.ClearingAccountAllocation") @@ -198,35 +209,36 @@ func init() { func init() { proto.RegisterFile("tx/pse/v1/distribution.proto", fileDescriptor_a549fe743b42ab69) } var fileDescriptor_a549fe743b42ab69 = []byte{ - // 442 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x92, 0x41, 0x6b, 0xd4, 0x40, - 0x14, 0xc7, 0x77, 0xaa, 0x14, 0x76, 0x8a, 0x58, 0xe2, 0x56, 0xdb, 0x55, 0x92, 0x12, 0x3c, 0xf4, - 0xb2, 0x33, 0xb4, 0x82, 0x82, 0x78, 0xd9, 0x28, 0x4a, 0x0f, 0x5e, 0x52, 0x4f, 0x5e, 0x96, 0xd9, - 0xc9, 0x90, 0x1d, 0x9a, 0xcc, 0x0c, 0x99, 0xb7, 0xcb, 0xd6, 0x4f, 0xe1, 0x37, 0xf1, 0xe2, 0xc5, - 0x6f, 0x50, 0x6f, 0xc5, 0x93, 0x78, 0x08, 0xb2, 0xfb, 0x0d, 0xf2, 0x09, 0x24, 0x99, 0x90, 0xd6, - 0x45, 0x6f, 0xbd, 0x25, 0xef, 0xff, 0x7f, 0x3f, 0xde, 0xff, 0xcd, 0xc3, 0x4f, 0x60, 0x49, 0x8d, - 0x15, 0x74, 0x71, 0x4c, 0x13, 0x69, 0xa1, 0x90, 0xd3, 0x39, 0x48, 0xad, 0x88, 0x29, 0x34, 0x68, - 0xaf, 0x0f, 0x4b, 0x62, 0xac, 0x20, 0x8b, 0xe3, 0xe1, 0x20, 0xd5, 0xa9, 0x6e, 0xaa, 0xb4, 0xfe, - 0x72, 0x86, 0xe1, 0x01, 0xd7, 0x36, 0xd7, 0x76, 0xe2, 0x04, 0xf7, 0xe3, 0xa4, 0xf0, 0x3b, 0xc2, - 0x0f, 0x5f, 0x67, 0x82, 0x15, 0x52, 0xa5, 0x63, 0xce, 0xf5, 0x5c, 0xc1, 0x7b, 0x66, 0x8c, 0x54, - 0xa9, 0xf7, 0x16, 0xef, 0xf2, 0x56, 0x99, 0x30, 0x27, 0xed, 0xa3, 0x43, 0x74, 0xd4, 0x8f, 0x1e, - 0x57, 0x65, 0xf0, 0xe8, 0x82, 0xe5, 0xd9, 0xcb, 0x70, 0xd3, 0x11, 0xc6, 0xf7, 0xf9, 0xdf, 0x38, - 0x2f, 0xc5, 0x0f, 0x0a, 0xc1, 0xa5, 0x91, 0x42, 0xc1, 0x84, 0x25, 0x49, 0x21, 0xac, 0x15, 0x76, - 0x7f, 0xeb, 0xf0, 0xce, 0x51, 0x3f, 0x7a, 0x5e, 0x95, 0xc1, 0xd0, 0xa1, 0xfe, 0x61, 0x0a, 0x7f, - 0x7c, 0x1d, 0x0d, 0xda, 0x79, 0xc7, 0xae, 0x78, 0x06, 0x35, 0x3b, 0xf6, 0x3a, 0xf7, 0xb8, 0x33, - 0x7f, 0x43, 0xf8, 0x60, 0x23, 0xcb, 0x38, 0xcb, 0x34, 0x67, 0xf5, 0xae, 0x6e, 0x2d, 0xce, 0x07, - 0xbc, 0xcd, 0xf2, 0xa6, 0x7b, 0xab, 0xe9, 0x7e, 0x75, 0x59, 0x06, 0xbd, 0x5f, 0x65, 0xb0, 0xe7, - 0xe6, 0xb4, 0xc9, 0x39, 0x91, 0x9a, 0xe6, 0x0c, 0x66, 0xe4, 0x54, 0x41, 0x55, 0x06, 0xf7, 0x1c, - 0xda, 0x35, 0xd5, 0x89, 0x70, 0x9b, 0xe8, 0x54, 0x41, 0xdc, 0xb2, 0xc2, 0x2f, 0x08, 0xef, 0x9d, - 0xf1, 0x99, 0x48, 0xe6, 0x99, 0x48, 0xde, 0xdc, 0x78, 0x63, 0xef, 0x04, 0xf7, 0x41, 0xe6, 0xc2, - 0x02, 0xcb, 0x4d, 0x33, 0xf0, 0xdd, 0x68, 0x50, 0x95, 0xc1, 0xae, 0xa3, 0x76, 0x52, 0x18, 0x5f, - 0xdb, 0xbc, 0x29, 0xde, 0x61, 0x5d, 0x72, 0xb7, 0xea, 0x9d, 0x93, 0xa7, 0xa4, 0xbb, 0x13, 0xf2, - 0xdf, 0x35, 0x45, 0xc3, 0x3a, 0x4e, 0x55, 0x06, 0x5e, 0x3b, 0xf5, 0x35, 0x26, 0x8c, 0x6f, 0x42, - 0xa3, 0x77, 0x97, 0x2b, 0x1f, 0x5d, 0xad, 0x7c, 0xf4, 0x7b, 0xe5, 0xa3, 0xcf, 0x6b, 0xbf, 0x77, - 0xb5, 0xf6, 0x7b, 0x3f, 0xd7, 0x7e, 0xef, 0xe3, 0x28, 0x95, 0x30, 0x9b, 0x4f, 0x09, 0xd7, 0x39, - 0x05, 0x7d, 0x2e, 0x94, 0xfc, 0x24, 0x46, 0x4b, 0x0a, 0xcb, 0x11, 0x9f, 0x31, 0xa9, 0xe8, 0xe2, - 0x05, 0x75, 0xe7, 0x0c, 0x17, 0x46, 0xd8, 0xe9, 0x76, 0x73, 0x89, 0xcf, 0xfe, 0x04, 0x00, 0x00, - 0xff, 0xff, 0x58, 0x32, 0x8e, 0xed, 0xe5, 0x02, 0x00, 0x00, + // 464 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x52, 0xcf, 0x6a, 0xd4, 0x40, + 0x18, 0xdf, 0xa4, 0x52, 0xc8, 0x14, 0xb1, 0xa4, 0x5b, 0xdd, 0xae, 0x92, 0x29, 0xd1, 0x43, 0x2f, + 0x9b, 0xa1, 0x15, 0x14, 0xc4, 0xcb, 0xc6, 0xa2, 0xec, 0xc1, 0x4b, 0xea, 0xc9, 0xcb, 0x32, 0x3b, + 0x33, 0x64, 0x87, 0x26, 0x33, 0x21, 0x33, 0xbb, 0x6c, 0x7d, 0x0a, 0x1f, 0xc6, 0x8b, 0x6f, 0x50, + 0x6f, 0x45, 0x3c, 0x88, 0x87, 0x41, 0x76, 0xdf, 0x20, 0x4f, 0x20, 0xc9, 0x84, 0xb4, 0x16, 0xbd, + 0xf5, 0x96, 0x7c, 0xbf, 0x3f, 0xfc, 0x7e, 0xdf, 0x7c, 0xe0, 0x89, 0x5e, 0xa1, 0x42, 0x31, 0xb4, + 0x3c, 0x46, 0x94, 0x2b, 0x5d, 0xf2, 0xd9, 0x42, 0x73, 0x29, 0xa2, 0xa2, 0x94, 0x5a, 0xfa, 0x9e, + 0x5e, 0x45, 0x85, 0x62, 0xd1, 0xf2, 0x78, 0xd8, 0x4f, 0x65, 0x2a, 0x9b, 0x29, 0xaa, 0xbf, 0x2c, + 0x61, 0x78, 0x40, 0xa4, 0xca, 0xa5, 0x9a, 0x5a, 0xc0, 0xfe, 0x58, 0x28, 0xfc, 0xe6, 0x80, 0x87, + 0x6f, 0x32, 0x86, 0x4b, 0x2e, 0xd2, 0x31, 0x21, 0x72, 0x21, 0xf4, 0x7b, 0x5c, 0x14, 0x5c, 0xa4, + 0xfe, 0x5b, 0xb0, 0x4b, 0x5a, 0x64, 0x8a, 0x2d, 0x34, 0x70, 0x0e, 0x9d, 0x23, 0x2f, 0x7e, 0x5c, + 0x19, 0xf8, 0xe8, 0x02, 0xe7, 0xd9, 0xab, 0xf0, 0x36, 0x23, 0x4c, 0x1e, 0x90, 0xbf, 0xed, 0xfc, + 0x14, 0xec, 0x95, 0x8c, 0xf0, 0x82, 0x33, 0xa1, 0xa7, 0x98, 0xd2, 0x92, 0x29, 0xc5, 0xd4, 0xc0, + 0x3d, 0xdc, 0x3a, 0xf2, 0xe2, 0x17, 0x95, 0x81, 0x43, 0x6b, 0xf5, 0x0f, 0x52, 0xf8, 0xfd, 0xcb, + 0xa8, 0xdf, 0xe6, 0x1d, 0xdb, 0xe1, 0x99, 0xae, 0xbd, 0x13, 0xbf, 0x63, 0x8f, 0x3b, 0xf2, 0x57, + 0x07, 0x1c, 0xdc, 0xea, 0x32, 0xce, 0x32, 0x49, 0x70, 0xbd, 0xab, 0x3b, 0xab, 0xf3, 0x01, 0x6c, + 0xe3, 0xbc, 0x51, 0xbb, 0x8d, 0xfa, 0xf5, 0xa5, 0x81, 0xbd, 0x5f, 0x06, 0xee, 0xdb, 0x9c, 0x8a, + 0x9e, 0x47, 0x5c, 0xa2, 0x1c, 0xeb, 0x79, 0x34, 0x11, 0xba, 0x32, 0xf0, 0xbe, 0xb5, 0xb6, 0xa2, + 0xba, 0x11, 0x68, 0x1b, 0x4d, 0x84, 0x4e, 0x5a, 0xaf, 0xf0, 0x87, 0x03, 0xf6, 0xcf, 0xc8, 0x9c, + 0xd1, 0x45, 0xc6, 0xe8, 0xe9, 0x8d, 0x37, 0xf6, 0x4f, 0x80, 0xa7, 0x79, 0xce, 0x94, 0xc6, 0x79, + 0xd1, 0x04, 0xbe, 0x17, 0xf7, 0x2b, 0x03, 0x77, 0xad, 0x6b, 0x07, 0x85, 0xc9, 0x35, 0xcd, 0x9f, + 0x81, 0x1d, 0xdc, 0x35, 0xb7, 0xab, 0xde, 0x39, 0x79, 0x16, 0x75, 0x77, 0x12, 0xfd, 0x77, 0x4d, + 0xf1, 0xb0, 0xae, 0x53, 0x19, 0xe8, 0xb7, 0xa9, 0xaf, 0x6d, 0xc2, 0xe4, 0xa6, 0xa9, 0xff, 0x14, + 0xb8, 0x9c, 0x0e, 0xb6, 0x9a, 0x40, 0x7b, 0x6b, 0x03, 0xdd, 0xc9, 0x69, 0x65, 0xa0, 0x67, 0x65, + 0x9c, 0x86, 0x89, 0xcb, 0x69, 0xfc, 0xee, 0x72, 0x1d, 0x38, 0x57, 0xeb, 0xc0, 0xf9, 0xbd, 0x0e, + 0x9c, 0xcf, 0x9b, 0xa0, 0x77, 0xb5, 0x09, 0x7a, 0x3f, 0x37, 0x41, 0xef, 0xe3, 0x28, 0xe5, 0x7a, + 0xbe, 0x98, 0x45, 0x44, 0xe6, 0x48, 0xcb, 0x73, 0x26, 0xf8, 0x27, 0x36, 0x5a, 0x21, 0xbd, 0x1a, + 0x91, 0x39, 0xe6, 0x02, 0x2d, 0x5f, 0x22, 0x7b, 0xf3, 0xfa, 0xa2, 0x60, 0x6a, 0xb6, 0xdd, 0x9c, + 0xeb, 0xf3, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xe3, 0xf0, 0x10, 0x7e, 0x0a, 0x03, 0x00, 0x00, } func (m *ClearingAccountMapping) Marshal() (dAtA []byte, err error) { @@ -328,6 +340,11 @@ func (m *ScheduledDistribution) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.ID != 0 { + i = encodeVarintDistribution(dAtA, i, uint64(m.ID)) + i-- + dAtA[i] = 0x18 + } if len(m.Allocations) > 0 { for iNdEx := len(m.Allocations) - 1; iNdEx >= 0; iNdEx-- { { @@ -410,6 +427,9 @@ func (m *ScheduledDistribution) Size() (n int) { n += 1 + l + sovDistribution(uint64(l)) } } + if m.ID != 0 { + n += 1 + sovDistribution(uint64(m.ID)) + } return n } @@ -731,6 +751,25 @@ func (m *ScheduledDistribution) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ID", wireType) + } + m.ID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDistribution + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipDistribution(dAtA[iNdEx:]) diff --git a/x/pse/types/genesis.go b/x/pse/types/genesis.go index f7a48b1c..8b99e571 100644 --- a/x/pse/types/genesis.go +++ b/x/pse/types/genesis.go @@ -27,6 +27,11 @@ func (m *GenesisState) Validate() error { return errorsmod.Wrapf(err, "invalid allocation schedule") } + // Validate minimum gap between distributions using the param from genesis state + if err := ValidateDistributionGap(m.ScheduledDistributions, m.Params.MinDistributionGapSeconds); err != nil { + return errorsmod.Wrapf(err, "invalid distribution gap") + } + // Validate delegation time entries for _, delegationTimeEntry := range m.DelegationTimeEntries { if delegationTimeEntry.ValidatorAddress == "" { diff --git a/x/pse/types/params.go b/x/pse/types/params.go index da11c089..f6663824 100644 --- a/x/pse/types/params.go +++ b/x/pse/types/params.go @@ -43,8 +43,9 @@ func GetNonCommunityClearingAccounts() []string { // DefaultParams returns default pse clearing account parameters. func DefaultParams() Params { return Params{ - ExcludedAddresses: []string{}, - ClearingAccountMappings: []ClearingAccountMapping{}, + ExcludedAddresses: []string{}, + ClearingAccountMappings: []ClearingAccountMapping{}, + MinDistributionGapSeconds: uint64(24 * 60 * 60), // 1 day } } @@ -128,26 +129,30 @@ func ValidateDistributionSchedule(schedule []ScheduledDistribution) error { // All clearing accounts (including Community) should be in the schedule allClearingAccounts := GetAllClearingAccounts() - seenTimestamps := make(map[uint64]bool) - var lastTime uint64 - for i, period := range schedule { - // Validate timestamp is not zero - if period.Timestamp == 0 { - return errorsmod.Wrapf(ErrInvalidParam, "timestamp cannot be zero") + // Validate id is non-zero + if period.ID == 0 { + return errorsmod.Wrapf(ErrInvalidParam, "period %d: id cannot be zero", i) } - // Check for duplicate timestamps - if seenTimestamps[period.Timestamp] { - return errorsmod.Wrapf(ErrInvalidParam, "duplicate timestamp") + // Validate timestamp is not zero + if period.Timestamp == 0 { + return errorsmod.Wrapf(ErrInvalidParam, "period %d: timestamp cannot be zero", i) } - seenTimestamps[period.Timestamp] = true - // Validate schedule is sorted in ascending order - if i > 0 && period.Timestamp <= lastTime { - return errorsmod.Wrapf(ErrInvalidParam, "periods must be sorted by timestamp in ascending order") + // Validate ordering against previous period: IDs must be sequential, timestamps must be monotonically increasing. + if i > 0 { + if period.ID != schedule[i-1].ID+1 { + return errorsmod.Wrapf(ErrInvalidParam, + "period %d: id must be sequential, expected %d but got %d", + i, schedule[i-1].ID+1, period.ID) + } + if period.Timestamp <= schedule[i-1].Timestamp { + return errorsmod.Wrapf(ErrInvalidParam, + "period %d: timestamp must be monotonically increasing, got %d after %d", + i, period.Timestamp, schedule[i-1].Timestamp) + } } - lastTime = period.Timestamp // Validate allocations array is not empty if len(period.Allocations) == 0 { @@ -227,3 +232,16 @@ func ValidateDistributionSchedule(schedule []ScheduledDistribution) error { return nil } + +// ValidateDistributionGap validates that consecutive distributions have at least minGapSeconds between them. +func ValidateDistributionGap(schedule []ScheduledDistribution, minGapSeconds uint64) error { + for i := 1; i < len(schedule); i++ { + gap := schedule[i].Timestamp - schedule[i-1].Timestamp + if gap < minGapSeconds { + return errorsmod.Wrapf(ErrInvalidParam, + "period %d: minimum gap between distributions is %d seconds, got %d seconds", + i, minGapSeconds, gap) + } + } + return nil +} diff --git a/x/pse/types/params.pb.go b/x/pse/types/params.pb.go index 8067742b..56ad25db 100644 --- a/x/pse/types/params.pb.go +++ b/x/pse/types/params.pb.go @@ -33,6 +33,8 @@ type Params struct { // clearing_account_mappings defines the mapping between clearing accounts and their sub accounts (multisig wallets). // These mappings can be modified via governance proposals. ClearingAccountMappings []ClearingAccountMapping `protobuf:"bytes,2,rep,name=clearing_account_mappings,json=clearingAccountMappings,proto3" json:"clearing_account_mappings" yaml:"clearing_account_mappings"` + // min_distribution_gap_seconds is the minimum required gap in seconds between consecutive distributions. + MinDistributionGapSeconds uint64 `protobuf:"varint,3,opt,name=min_distribution_gap_seconds,json=minDistributionGapSeconds,proto3" json:"min_distribution_gap_seconds,omitempty" yaml:"min_distribution_gap_seconds"` } func (m *Params) Reset() { *m = Params{} } @@ -82,6 +84,13 @@ func (m *Params) GetClearingAccountMappings() []ClearingAccountMapping { return nil } +func (m *Params) GetMinDistributionGapSeconds() uint64 { + if m != nil { + return m.MinDistributionGapSeconds + } + return 0 +} + func init() { proto.RegisterType((*Params)(nil), "tx.pse.v1.Params") } @@ -89,28 +98,31 @@ func init() { func init() { proto.RegisterFile("tx/pse/v1/params.proto", fileDescriptor_b70a3fad281b1b5f) } var fileDescriptor_b70a3fad281b1b5f = []byte{ - // 324 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x90, 0x31, 0x4f, 0x32, 0x31, - 0x1c, 0xc6, 0xef, 0x5e, 0x12, 0x12, 0xee, 0x9d, 0xbc, 0x10, 0x05, 0x62, 0x0e, 0xbc, 0x89, 0xe5, - 0xda, 0xa0, 0x31, 0x26, 0x6e, 0xe0, 0xe0, 0x64, 0x62, 0x70, 0x73, 0xb9, 0x94, 0xb6, 0x39, 0x1a, - 0xb9, 0xb6, 0xb9, 0x7f, 0x8f, 0x1c, 0x7e, 0x01, 0x57, 0x3f, 0x8c, 0x1f, 0x82, 0x91, 0x38, 0x39, - 0x11, 0x03, 0xdf, 0x80, 0xc9, 0xd1, 0x40, 0x0f, 0x1d, 0xd4, 0xad, 0x7d, 0x9e, 0x5f, 0x9e, 0x3e, - 0x7d, 0xbc, 0x43, 0x53, 0x60, 0x0d, 0x1c, 0x4f, 0x7b, 0x58, 0x93, 0x8c, 0xa4, 0x80, 0x74, 0xa6, - 0x8c, 0xf2, 0x6b, 0xa6, 0x40, 0x1a, 0x38, 0x9a, 0xf6, 0x5a, 0x4d, 0xaa, 0x20, 0x55, 0x10, 0xef, - 0x0c, 0x6c, 0x2f, 0x96, 0x6a, 0xd5, 0x13, 0x95, 0x28, 0xab, 0x6f, 0x4f, 0xa5, 0x7a, 0xfc, 0x9d, - 0xc9, 0x04, 0x98, 0x4c, 0x8c, 0x72, 0x23, 0x94, 0xb4, 0x6e, 0xf8, 0xe1, 0x7a, 0xd5, 0xdb, 0xdd, - 0x53, 0x3e, 0xf3, 0x7c, 0x5e, 0xd0, 0x49, 0xce, 0x38, 0x8b, 0x09, 0x63, 0x19, 0x07, 0xe0, 0xd0, - 0x70, 0x3b, 0x95, 0x6e, 0x6d, 0x70, 0xbe, 0x59, 0xb6, 0x9b, 0x33, 0x92, 0x4e, 0x2e, 0xc3, 0x9f, - 0x4c, 0xf8, 0xfa, 0x12, 0xd5, 0xcb, 0x26, 0x7d, 0x2b, 0xde, 0x99, 0x4c, 0xc8, 0x64, 0x78, 0xb0, - 0x87, 0xfb, 0x7b, 0xd6, 0x7f, 0x72, 0xbd, 0x26, 0x9d, 0x70, 0xb2, 0xf5, 0x63, 0x42, 0xa9, 0xca, - 0xa5, 0x89, 0x53, 0xa2, 0xb5, 0x90, 0x09, 0x34, 0xfe, 0x75, 0x2a, 0xdd, 0xff, 0xa7, 0x27, 0xe8, - 0xeb, 0xbf, 0xe8, 0xaa, 0x64, 0xfb, 0x16, 0xbd, 0xb1, 0xe4, 0xa0, 0x3b, 0x5f, 0xb6, 0x9d, 0xcd, - 0xb2, 0xdd, 0xb1, 0xa5, 0xfe, 0x4c, 0x0c, 0x87, 0x47, 0xf4, 0xd7, 0x04, 0x18, 0x5c, 0xcf, 0x57, - 0x81, 0xbb, 0x58, 0x05, 0xee, 0xfb, 0x2a, 0x70, 0x9f, 0xd7, 0x81, 0xb3, 0x58, 0x07, 0xce, 0xdb, - 0x3a, 0x70, 0xee, 0xa3, 0x44, 0x98, 0x71, 0x3e, 0x42, 0x54, 0xa5, 0xd8, 0xa8, 0x07, 0x2e, 0xc5, - 0x23, 0x8f, 0x0a, 0x6c, 0x8a, 0x88, 0x8e, 0x89, 0x90, 0x78, 0x7a, 0x81, 0xed, 0xa6, 0x66, 0xa6, - 0x39, 0x8c, 0xaa, 0xbb, 0x29, 0xcf, 0x3e, 0x03, 0x00, 0x00, 0xff, 0xff, 0xca, 0xf7, 0xa8, 0xea, - 0xbe, 0x01, 0x00, 0x00, + // 372 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x91, 0x4f, 0x6b, 0xdb, 0x30, + 0x18, 0x87, 0xed, 0x64, 0x04, 0xe2, 0x9d, 0x66, 0xc2, 0x16, 0x87, 0x60, 0x7b, 0xde, 0x61, 0xbe, + 0xd8, 0x22, 0x1b, 0x63, 0xb0, 0x5b, 0xbc, 0x42, 0x4e, 0x85, 0x92, 0xdc, 0x7a, 0x31, 0x8a, 0x2c, + 0x1c, 0xd1, 0x58, 0x12, 0x96, 0x1c, 0x9c, 0x7e, 0x81, 0x5e, 0xfb, 0x61, 0xfa, 0x21, 0x72, 0x0c, + 0x3d, 0xf5, 0x64, 0x8a, 0xf3, 0x0d, 0xf2, 0x09, 0x4a, 0xa2, 0xa4, 0x0d, 0xf4, 0xcf, 0x4d, 0x7a, + 0x9f, 0x87, 0xdf, 0xfb, 0x4a, 0xaf, 0xf1, 0x55, 0x96, 0x80, 0x0b, 0x0c, 0x16, 0x03, 0xc0, 0x61, + 0x0e, 0x33, 0x11, 0xf2, 0x9c, 0x49, 0x66, 0xb6, 0x65, 0x19, 0x72, 0x81, 0xc3, 0xc5, 0xa0, 0x67, + 0x21, 0x26, 0x32, 0x26, 0xe2, 0x3d, 0x00, 0xea, 0xa2, 0xac, 0x5e, 0x27, 0x65, 0x29, 0x53, 0xf5, + 0xdd, 0xe9, 0x50, 0xed, 0xbf, 0x64, 0x26, 0x44, 0xc8, 0x9c, 0x4c, 0x0b, 0x49, 0x18, 0x55, 0xd4, + 0xab, 0x1b, 0x46, 0xeb, 0x62, 0xdf, 0xca, 0x4c, 0x0c, 0x13, 0x97, 0x68, 0x5e, 0x24, 0x38, 0x89, + 0x61, 0x92, 0xe4, 0x58, 0x08, 0x2c, 0xba, 0xba, 0xdb, 0xf4, 0xdb, 0xd1, 0x9f, 0x6d, 0xe5, 0x58, + 0x4b, 0x98, 0xcd, 0xff, 0x79, 0xaf, 0x1d, 0xef, 0xfe, 0x2e, 0xe8, 0x1c, 0x26, 0x19, 0xaa, 0xe2, + 0x44, 0xe6, 0x84, 0xa6, 0xe3, 0x2f, 0x47, 0x79, 0x78, 0x74, 0xcd, 0x1b, 0xdd, 0xb0, 0xd0, 0x1c, + 0xc3, 0x1d, 0x8f, 0x21, 0x42, 0xac, 0xa0, 0x32, 0xce, 0x20, 0xe7, 0x84, 0xa6, 0xa2, 0xdb, 0x70, + 0x9b, 0xfe, 0xe7, 0x5f, 0xdf, 0xc3, 0xe7, 0xf7, 0x86, 0xff, 0x0f, 0xee, 0x50, 0xa9, 0xe7, 0xca, + 0x8c, 0xfc, 0x55, 0xe5, 0x68, 0xdb, 0xca, 0x71, 0xd5, 0x50, 0xef, 0x26, 0x7a, 0xe3, 0x6f, 0xe8, + 0xcd, 0x04, 0x61, 0xce, 0x8c, 0x7e, 0x46, 0x68, 0x7c, 0xfa, 0x29, 0x71, 0x0a, 0x79, 0x2c, 0x30, + 0x62, 0x34, 0x11, 0xdd, 0xa6, 0xab, 0xfb, 0x9f, 0xa2, 0x9f, 0xdb, 0xca, 0xf9, 0xa1, 0x9a, 0x7c, + 0x64, 0x7b, 0x63, 0x2b, 0x23, 0xf4, 0xec, 0x84, 0x8e, 0x20, 0x9f, 0x28, 0x16, 0x8d, 0x56, 0xb5, + 0xad, 0xaf, 0x6b, 0x5b, 0x7f, 0xac, 0x6d, 0xfd, 0x76, 0x63, 0x6b, 0xeb, 0x8d, 0xad, 0x3d, 0x6c, + 0x6c, 0xed, 0x32, 0x48, 0x89, 0x9c, 0x15, 0xd3, 0x10, 0xb1, 0x0c, 0x48, 0x76, 0x85, 0x29, 0xb9, + 0xc6, 0x41, 0x09, 0x64, 0x19, 0xa0, 0x19, 0x24, 0x14, 0x2c, 0xfe, 0x02, 0xb5, 0x3d, 0xb9, 0xe4, + 0x58, 0x4c, 0x5b, 0xfb, 0xa5, 0xfd, 0x7e, 0x0a, 0x00, 0x00, 0xff, 0xff, 0x24, 0xb9, 0x0e, 0x8a, + 0x28, 0x02, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -133,6 +145,11 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.MinDistributionGapSeconds != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.MinDistributionGapSeconds)) + i-- + dAtA[i] = 0x18 + } if len(m.ClearingAccountMappings) > 0 { for iNdEx := len(m.ClearingAccountMappings) - 1; iNdEx >= 0; iNdEx-- { { @@ -188,6 +205,9 @@ func (m *Params) Size() (n int) { n += 1 + l + sovParams(uint64(l)) } } + if m.MinDistributionGapSeconds != 0 { + n += 1 + sovParams(uint64(m.MinDistributionGapSeconds)) + } return n } @@ -292,6 +312,25 @@ func (m *Params) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MinDistributionGapSeconds", wireType) + } + m.MinDistributionGapSeconds = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MinDistributionGapSeconds |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) diff --git a/x/pse/types/params_test.go b/x/pse/types/params_test.go index d16e3c26..cd7467b2 100644 --- a/x/pse/types/params_test.go +++ b/x/pse/types/params_test.go @@ -313,6 +313,7 @@ func TestValidateAllocationSchedule(t *testing.T) { name: "valid_single_period", schedule: []ScheduledDistribution{ { + ID: 1, Timestamp: getTestTimestamp(0), Allocations: createAllModuleAllocations(sdkmath.NewInt(1000)), }, @@ -323,10 +324,12 @@ func TestValidateAllocationSchedule(t *testing.T) { name: "valid_multiple_periods_sorted", schedule: []ScheduledDistribution{ { + ID: 1, Timestamp: getTestTimestamp(0), Allocations: createAllModuleAllocations(sdkmath.NewInt(1000)), }, { + ID: 2, Timestamp: getTestTimestamp(12), Allocations: createAllModuleAllocations(sdkmath.NewInt(2000)), }, @@ -337,6 +340,7 @@ func TestValidateAllocationSchedule(t *testing.T) { name: "valid_with_community_account", schedule: []ScheduledDistribution{ { + ID: 1, Timestamp: getTestTimestamp(0), Allocations: []ClearingAccountAllocation{ // All clearing accounts (including Community) should be in schedule @@ -351,10 +355,40 @@ func TestValidateAllocationSchedule(t *testing.T) { }, expectErr: false, }, + { + name: "invalid_zero_id", + schedule: []ScheduledDistribution{ + { + ID: 0, + Timestamp: getTestTimestamp(0), + Allocations: createAllModuleAllocations(sdkmath.NewInt(1000)), + }, + }, + expectErr: true, + errMsg: "id cannot be zero", + }, + { + name: "invalid_non_sequential_id", + schedule: []ScheduledDistribution{ + { + ID: 1, + Timestamp: getTestTimestamp(0), + Allocations: createAllModuleAllocations(sdkmath.NewInt(1000)), + }, + { + ID: 5, + Timestamp: getTestTimestamp(12), + Allocations: createAllModuleAllocations(sdkmath.NewInt(2000)), + }, + }, + expectErr: true, + errMsg: "id must be sequential", + }, { name: "invalid_zero_timestamp", schedule: []ScheduledDistribution{ { + ID: 1, Timestamp: 0, Allocations: []ClearingAccountAllocation{ {ClearingAccount: ClearingAccountFoundation, Amount: sdkmath.NewInt(1000)}, @@ -368,36 +402,41 @@ func TestValidateAllocationSchedule(t *testing.T) { name: "invalid_duplicate_timestamp", schedule: []ScheduledDistribution{ { + ID: 1, Timestamp: getTestTimestamp(0), Allocations: createAllModuleAllocations(sdkmath.NewInt(1000)), }, { + ID: 2, Timestamp: getTestTimestamp(0), Allocations: createAllModuleAllocations(sdkmath.NewInt(2000)), }, }, expectErr: true, - errMsg: "duplicate timestamp", + errMsg: "timestamp must be monotonically increasing", }, { name: "invalid_unsorted_schedule", schedule: []ScheduledDistribution{ { + ID: 1, Timestamp: getTestTimestamp(12), Allocations: createAllModuleAllocations(sdkmath.NewInt(2000)), }, { + ID: 2, Timestamp: getTestTimestamp(0), Allocations: createAllModuleAllocations(sdkmath.NewInt(1000)), }, }, expectErr: true, - errMsg: "must be sorted by timestamp in ascending order", + errMsg: "timestamp must be monotonically increasing", }, { name: "invalid_empty_allocations_array", schedule: []ScheduledDistribution{ { + ID: 1, Timestamp: getTestTimestamp(0), Allocations: []ClearingAccountAllocation{}, }, @@ -409,6 +448,7 @@ func TestValidateAllocationSchedule(t *testing.T) { name: "invalid_too_few_allocations", schedule: []ScheduledDistribution{ { + ID: 1, Timestamp: getTestTimestamp(0), Allocations: []ClearingAccountAllocation{ {ClearingAccount: ClearingAccountFoundation, Amount: sdkmath.NewInt(1000)}, @@ -422,6 +462,7 @@ func TestValidateAllocationSchedule(t *testing.T) { name: "invalid_empty_clearing_account", schedule: []ScheduledDistribution{ { + ID: 1, Timestamp: getTestTimestamp(0), Allocations: []ClearingAccountAllocation{ {ClearingAccount: "", Amount: sdkmath.NewInt(1000)}, @@ -439,6 +480,7 @@ func TestValidateAllocationSchedule(t *testing.T) { name: "invalid_unknown_clearing_account", schedule: []ScheduledDistribution{ { + ID: 1, Timestamp: getTestTimestamp(0), Allocations: []ClearingAccountAllocation{ {ClearingAccount: "unknown_module", Amount: sdkmath.NewInt(1000)}, @@ -458,6 +500,7 @@ func TestValidateAllocationSchedule(t *testing.T) { name: "invalid_duplicate_clearing_account_in_period", schedule: []ScheduledDistribution{ { + ID: 1, Timestamp: getTestTimestamp(0), Allocations: []ClearingAccountAllocation{ {ClearingAccount: ClearingAccountFoundation, Amount: sdkmath.NewInt(1000)}, @@ -476,6 +519,7 @@ func TestValidateAllocationSchedule(t *testing.T) { name: "invalid_missing_clearing_account", schedule: []ScheduledDistribution{ { + ID: 1, Timestamp: getTestTimestamp(0), Allocations: []ClearingAccountAllocation{ {ClearingAccount: ClearingAccountFoundation, Amount: sdkmath.NewInt(1000)}, @@ -493,6 +537,7 @@ func TestValidateAllocationSchedule(t *testing.T) { name: "invalid_nil_amount", schedule: []ScheduledDistribution{ { + ID: 1, Timestamp: getTestTimestamp(0), Allocations: []ClearingAccountAllocation{ {ClearingAccount: ClearingAccountFoundation, Amount: sdkmath.Int{}}, @@ -510,6 +555,7 @@ func TestValidateAllocationSchedule(t *testing.T) { name: "invalid_negative_amount", schedule: []ScheduledDistribution{ { + ID: 1, Timestamp: getTestTimestamp(0), Allocations: []ClearingAccountAllocation{ {ClearingAccount: ClearingAccountFoundation, Amount: sdkmath.NewInt(-1000)}, @@ -527,6 +573,7 @@ func TestValidateAllocationSchedule(t *testing.T) { name: "invalid_zero_amount", schedule: []ScheduledDistribution{ { + ID: 1, Timestamp: getTestTimestamp(0), Allocations: []ClearingAccountAllocation{ {ClearingAccount: ClearingAccountFoundation, Amount: sdkmath.ZeroInt()}, diff --git a/x/pse/types/tx.pb.go b/x/pse/types/tx.pb.go index 6f1c133b..f5755205 100644 --- a/x/pse/types/tx.pb.go +++ b/x/pse/types/tx.pb.go @@ -254,6 +254,63 @@ func (m *MsgUpdateDistributionSchedule) GetSchedule() []ScheduledDistribution { return nil } +// MsgUpdateMinDistributionGap is a governance operation to update the minimum time gap +// between consecutive scheduled distributions. The new gap is validated against the +// existing on-chain schedule to ensure consistency. +type MsgUpdateMinDistributionGap struct { + // authority is the address authorized to update the gap (governance module address). + Authority string `protobuf:"bytes,1,opt,name=authority,proto3" json:"authority,omitempty"` + // min_distribution_gap_seconds is the minimum time gap (in seconds) between consecutive distributions. + MinDistributionGapSeconds uint64 `protobuf:"varint,2,opt,name=min_distribution_gap_seconds,json=minDistributionGapSeconds,proto3" json:"min_distribution_gap_seconds,omitempty" yaml:"min_distribution_gap_seconds"` +} + +func (m *MsgUpdateMinDistributionGap) Reset() { *m = MsgUpdateMinDistributionGap{} } +func (m *MsgUpdateMinDistributionGap) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateMinDistributionGap) ProtoMessage() {} +func (*MsgUpdateMinDistributionGap) Descriptor() ([]byte, []int) { + return fileDescriptor_7fbcd921f59054cd, []int{4} +} +func (m *MsgUpdateMinDistributionGap) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateMinDistributionGap) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateMinDistributionGap.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 *MsgUpdateMinDistributionGap) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateMinDistributionGap.Merge(m, src) +} +func (m *MsgUpdateMinDistributionGap) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateMinDistributionGap) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateMinDistributionGap.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateMinDistributionGap proto.InternalMessageInfo + +func (m *MsgUpdateMinDistributionGap) GetAuthority() string { + if m != nil { + return m.Authority + } + return "" +} + +func (m *MsgUpdateMinDistributionGap) GetMinDistributionGapSeconds() uint64 { + if m != nil { + return m.MinDistributionGapSeconds + } + return 0 +} + type EmptyResponse struct { } @@ -261,7 +318,7 @@ func (m *EmptyResponse) Reset() { *m = EmptyResponse{} } func (m *EmptyResponse) String() string { return proto.CompactTextString(m) } func (*EmptyResponse) ProtoMessage() {} func (*EmptyResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_7fbcd921f59054cd, []int{4} + return fileDescriptor_7fbcd921f59054cd, []int{5} } func (m *EmptyResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -295,51 +352,57 @@ func init() { proto.RegisterType((*MsgUpdateExcludedAddresses)(nil), "tx.pse.v1.MsgUpdateExcludedAddresses") proto.RegisterType((*MsgUpdateClearingAccountMappings)(nil), "tx.pse.v1.MsgUpdateClearingAccountMappings") proto.RegisterType((*MsgUpdateDistributionSchedule)(nil), "tx.pse.v1.MsgUpdateDistributionSchedule") + proto.RegisterType((*MsgUpdateMinDistributionGap)(nil), "tx.pse.v1.MsgUpdateMinDistributionGap") proto.RegisterType((*EmptyResponse)(nil), "tx.pse.v1.EmptyResponse") } func init() { proto.RegisterFile("tx/pse/v1/tx.proto", fileDescriptor_7fbcd921f59054cd) } var fileDescriptor_7fbcd921f59054cd = []byte{ - // 605 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0x31, 0x6f, 0xda, 0x40, - 0x18, 0x86, 0x71, 0x50, 0xab, 0x70, 0x55, 0x9b, 0xc6, 0x45, 0x82, 0xba, 0x8d, 0x21, 0x56, 0x2b, - 0x21, 0x2a, 0x6c, 0x91, 0x54, 0x89, 0xc4, 0x06, 0x4d, 0xd4, 0x89, 0x85, 0x24, 0x1d, 0x22, 0x55, - 0xd4, 0xf8, 0x2e, 0xe6, 0x54, 0xec, 0xb3, 0x7c, 0x67, 0x04, 0x9d, 0xda, 0x4e, 0x55, 0xa7, 0xfe, - 0x14, 0x86, 0xfe, 0x88, 0x4c, 0x55, 0xd4, 0xa9, 0x13, 0xaa, 0xa0, 0x12, 0x43, 0x37, 0x7e, 0x41, - 0x84, 0x6d, 0x8c, 0x09, 0x36, 0x03, 0x0b, 0xba, 0xe3, 0x7b, 0xef, 0x7d, 0xee, 0xfd, 0xf8, 0x38, - 0xc0, 0xb3, 0x9e, 0x62, 0x51, 0xa4, 0x74, 0xcb, 0x0a, 0xeb, 0xc9, 0x96, 0x4d, 0x18, 0xe1, 0x53, - 0xb3, 0x15, 0x45, 0x72, 0xb7, 0x2c, 0xec, 0xaa, 0x06, 0x36, 0x89, 0xe2, 0x7e, 0x7a, 0x55, 0x21, - 0xad, 0x13, 0x9d, 0xb8, 0x4b, 0x65, 0xb6, 0xf2, 0xbf, 0x7d, 0xaa, 0x11, 0x6a, 0x10, 0xda, 0xf4, - 0x0a, 0xde, 0xc6, 0x2f, 0x65, 0xbc, 0x9d, 0x62, 0x50, 0x7d, 0x86, 0x31, 0xa8, 0xee, 0x17, 0x9e, - 0x2f, 0xd8, 0x10, 0x53, 0x66, 0xe3, 0x96, 0xc3, 0x30, 0x31, 0xbd, 0xaa, 0xf4, 0x85, 0x03, 0x99, - 0x3a, 0xd5, 0x4f, 0x30, 0x55, 0x5b, 0x1d, 0x74, 0x12, 0x12, 0x50, 0xfe, 0x08, 0xa4, 0x54, 0x87, - 0xb5, 0x89, 0x8d, 0x59, 0x3f, 0xcb, 0xe5, 0xb9, 0x42, 0xaa, 0x96, 0xfd, 0xfd, 0xb3, 0x94, 0xf6, - 0xb9, 0x55, 0x08, 0x6d, 0x44, 0xe9, 0x19, 0xb3, 0xb1, 0xa9, 0x37, 0x16, 0xd2, 0x8a, 0xfc, 0x75, - 0x32, 0x28, 0x2e, 0xf6, 0xdf, 0x27, 0x83, 0xe2, 0xb3, 0xd9, 0x0d, 0x62, 0x38, 0xd2, 0xaf, 0x2d, - 0x20, 0xd4, 0xa9, 0x7e, 0x61, 0x41, 0x95, 0xa1, 0xd3, 0x9e, 0xd6, 0x71, 0x20, 0x82, 0xbe, 0x3b, - 0xda, 0xf8, 0x1a, 0xfc, 0x7b, 0xf0, 0x58, 0x9d, 0x9b, 0x34, 0x19, 0x69, 0xaa, 0x10, 0x66, 0xb7, - 0xf2, 0xc9, 0x42, 0xaa, 0x76, 0x38, 0x1d, 0xe6, 0x32, 0x7d, 0xd5, 0xe8, 0x54, 0xa4, 0xbb, 0x0a, - 0x29, 0xd6, 0xf9, 0x51, 0x20, 0x3d, 0x27, 0x55, 0x08, 0xf9, 0x2b, 0xf0, 0x64, 0xe9, 0xb0, 0x8d, - 0x0c, 0xd2, 0x45, 0xd9, 0xa4, 0x4b, 0x38, 0x9a, 0x0e, 0x73, 0x42, 0x04, 0xc1, 0x13, 0xc5, 0x43, - 0x76, 0x43, 0x90, 0x86, 0xab, 0xad, 0x94, 0x57, 0xbb, 0x29, 0xfa, 0xdd, 0x8c, 0xe9, 0x98, 0xf4, - 0x9f, 0x03, 0xf9, 0xa0, 0xfc, 0xa6, 0x83, 0xd4, 0x99, 0x77, 0x55, 0xd3, 0x88, 0x63, 0xb2, 0xba, - 0x6a, 0x59, 0xd8, 0xd4, 0x37, 0x6f, 0xeb, 0x3b, 0xb0, 0x6d, 0xf8, 0x1e, 0x6e, 0x3b, 0x1f, 0x1c, - 0xec, 0xcb, 0xc1, 0x28, 0xcb, 0xd1, 0xb4, 0x5a, 0xe6, 0x7a, 0x98, 0x4b, 0x4c, 0x87, 0xb9, 0x1d, - 0xaf, 0x27, 0x73, 0x03, 0xa9, 0x11, 0x78, 0x55, 0x8e, 0x57, 0x73, 0xbe, 0x58, 0xca, 0x19, 0x13, - 0x44, 0xfa, 0xc7, 0x81, 0xbd, 0x40, 0x14, 0x9e, 0xac, 0x33, 0xad, 0x8d, 0xa0, 0xd3, 0x41, 0x1b, - 0x47, 0xbd, 0x00, 0xdb, 0xd4, 0xf7, 0xf0, 0xa3, 0xe6, 0x43, 0x51, 0xe7, 0xf6, 0x30, 0xcc, 0xbc, - 0x9b, 0x74, 0x7e, 0x5e, 0x6a, 0x04, 0x56, 0x95, 0xd7, 0xab, 0x49, 0xf7, 0x97, 0x92, 0x46, 0x85, - 0x90, 0x76, 0xc0, 0xc3, 0x53, 0xc3, 0x62, 0xfd, 0x06, 0xa2, 0x16, 0x31, 0x29, 0x3a, 0xf8, 0x96, - 0x04, 0xc9, 0x3a, 0xd5, 0xf9, 0x4b, 0x90, 0x89, 0xfb, 0xeb, 0xbc, 0x0c, 0x5d, 0x37, 0x7e, 0x5e, - 0x84, 0x6c, 0x48, 0xb6, 0xc4, 0xe0, 0xaf, 0xc0, 0xde, 0xfa, 0x29, 0x7a, 0x15, 0x45, 0x88, 0x11, - 0xaf, 0xe1, 0x7c, 0x00, 0xc2, 0x9a, 0xdf, 0xaf, 0x10, 0x05, 0x89, 0x52, 0xae, 0x21, 0x9c, 0x83, - 0x74, 0xe4, 0x23, 0x27, 0x2d, 0x7b, 0x47, 0x69, 0xe2, 0x5d, 0x85, 0x7b, 0x9f, 0x27, 0x83, 0x22, - 0x57, 0x7b, 0x7b, 0x3d, 0x12, 0xb9, 0x9b, 0x91, 0xc8, 0xfd, 0x1d, 0x89, 0xdc, 0x8f, 0xb1, 0x98, - 0xb8, 0x19, 0x8b, 0x89, 0x3f, 0x63, 0x31, 0x71, 0x59, 0xd2, 0x31, 0x6b, 0x3b, 0x2d, 0x59, 0x23, - 0x86, 0xc2, 0xc8, 0x47, 0x64, 0xe2, 0x4f, 0xa8, 0xd4, 0x53, 0x58, 0xaf, 0xa4, 0xb5, 0x55, 0x6c, - 0x2a, 0xdd, 0x63, 0xc5, 0x7b, 0x9e, 0x59, 0xdf, 0x42, 0xb4, 0x75, 0xdf, 0x7d, 0x95, 0x0f, 0x6f, - 0x03, 0x00, 0x00, 0xff, 0xff, 0x96, 0xa7, 0x0d, 0x6a, 0x31, 0x06, 0x00, 0x00, + // 685 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x55, 0x4f, 0x4f, 0xdb, 0x3e, + 0x18, 0x6e, 0xe8, 0xef, 0x37, 0x51, 0x4f, 0x1b, 0x23, 0x43, 0x6a, 0x09, 0x90, 0x16, 0xef, 0x5f, + 0xc5, 0xd4, 0x46, 0xc0, 0x04, 0x52, 0x6f, 0x74, 0x20, 0x4e, 0xbd, 0x14, 0xd8, 0x01, 0x6d, 0xea, + 0xdc, 0xd8, 0xa4, 0xd6, 0x9a, 0x38, 0xaa, 0xdd, 0xaa, 0xdd, 0x69, 0xdb, 0x71, 0xa7, 0x7d, 0x14, + 0x0e, 0xfb, 0x10, 0x9c, 0x26, 0xb4, 0xd3, 0x4e, 0xd5, 0x04, 0xd3, 0x38, 0xec, 0xd6, 0x7d, 0x81, + 0xa9, 0x4d, 0x9a, 0xa6, 0xe4, 0xcf, 0xa1, 0x97, 0xc8, 0xce, 0xfb, 0xf8, 0x79, 0xde, 0xe7, 0xf5, + 0x6b, 0x1b, 0xc8, 0xa2, 0xab, 0xd9, 0x9c, 0x68, 0x9d, 0x4d, 0x4d, 0x74, 0x8b, 0x76, 0x8b, 0x09, + 0x26, 0xa7, 0x86, 0x23, 0x4e, 0x8a, 0x9d, 0x4d, 0x65, 0x11, 0x99, 0xd4, 0x62, 0xda, 0xe8, 0xeb, + 0x44, 0x95, 0x25, 0x83, 0x19, 0x6c, 0x34, 0xd4, 0x86, 0x23, 0xf7, 0xef, 0xb2, 0xce, 0xb8, 0xc9, + 0x78, 0xcd, 0x09, 0x38, 0x13, 0x37, 0x94, 0x76, 0x66, 0x9a, 0xc9, 0x8d, 0xa1, 0x8c, 0xc9, 0x0d, + 0x37, 0xb0, 0x3a, 0xd1, 0xc6, 0x94, 0x8b, 0x16, 0xad, 0xb7, 0x05, 0x65, 0x96, 0x13, 0x85, 0x1f, + 0x25, 0x90, 0xae, 0x70, 0x63, 0x9f, 0x72, 0x54, 0x6f, 0x92, 0x7d, 0x1f, 0x80, 0xcb, 0x3b, 0x20, + 0x85, 0xda, 0xa2, 0xc1, 0x5a, 0x54, 0xf4, 0x32, 0x52, 0x4e, 0xca, 0xa7, 0xca, 0x99, 0xef, 0x5f, + 0x0b, 0x4b, 0xae, 0xee, 0x1e, 0xc6, 0x2d, 0xc2, 0xf9, 0x91, 0x68, 0x51, 0xcb, 0xa8, 0x4e, 0xa0, + 0xa5, 0xe2, 0xa7, 0x9b, 0xf3, 0x8d, 0xc9, 0xfc, 0xf3, 0xcd, 0xf9, 0xc6, 0xca, 0x30, 0x83, 0x08, + 0x1d, 0xf8, 0x6d, 0x0e, 0x28, 0x15, 0x6e, 0x9c, 0xd8, 0x18, 0x09, 0x72, 0xd0, 0xd5, 0x9b, 0x6d, + 0x4c, 0xb0, 0xcb, 0x4e, 0x66, 0x4e, 0x43, 0x7e, 0x03, 0x1e, 0xa0, 0x31, 0x49, 0x4d, 0xb0, 0x1a, + 0xc2, 0x38, 0x33, 0x97, 0x4b, 0xe6, 0x53, 0xe5, 0xed, 0x41, 0x3f, 0x9b, 0xee, 0x21, 0xb3, 0x59, + 0x82, 0xb7, 0x11, 0x30, 0x92, 0xf9, 0xbe, 0x07, 0x3d, 0x66, 0x7b, 0x18, 0xcb, 0x67, 0xe0, 0xe1, + 0xd4, 0xe2, 0x16, 0x31, 0x59, 0x87, 0x64, 0x92, 0x23, 0x85, 0x9d, 0x41, 0x3f, 0xab, 0x84, 0x28, + 0x38, 0xa0, 0x68, 0x91, 0x45, 0x9f, 0x48, 0x75, 0x84, 0x2d, 0x6d, 0x06, 0xab, 0xa9, 0xba, 0xd5, + 0x8c, 0xa8, 0x18, 0xfc, 0x23, 0x81, 0x9c, 0x17, 0x7e, 0xd9, 0x24, 0x68, 0xc8, 0xbd, 0xa7, 0xeb, + 0xac, 0x6d, 0x89, 0x0a, 0xb2, 0x6d, 0x6a, 0x19, 0xb3, 0x97, 0xf5, 0x15, 0x98, 0x37, 0x5d, 0x8e, + 0x51, 0x39, 0xef, 0x6e, 0xad, 0x17, 0xbd, 0x56, 0x2e, 0x86, 0xab, 0x95, 0xd3, 0x17, 0xfd, 0x6c, + 0x62, 0xd0, 0xcf, 0x2e, 0x38, 0x35, 0x19, 0x13, 0xc0, 0xaa, 0xc7, 0x55, 0xda, 0x0d, 0xfa, 0x7c, + 0x3c, 0xe5, 0x33, 0xc2, 0x08, 0xfc, 0x25, 0x81, 0x35, 0x0f, 0xe4, 0xef, 0xac, 0x23, 0xbd, 0x41, + 0x70, 0xbb, 0x49, 0x66, 0xb6, 0x7a, 0x02, 0xe6, 0xb9, 0xcb, 0xe1, 0x5a, 0xcd, 0xf9, 0xac, 0x8e, + 0xe9, 0xb1, 0x5f, 0xf3, 0xb6, 0xd3, 0xf1, 0x7a, 0x58, 0xf5, 0xa8, 0x4a, 0x2f, 0x82, 0x4e, 0xd7, + 0xa7, 0x9c, 0x86, 0x99, 0x80, 0x7f, 0x25, 0xb0, 0xe2, 0x21, 0x2a, 0xd4, 0xf2, 0x83, 0x0e, 0x91, + 0x3d, 0xb3, 0xc9, 0x06, 0x58, 0x35, 0xa9, 0x55, 0xf3, 0xdf, 0x0d, 0x35, 0x03, 0xd9, 0x35, 0x4e, + 0x74, 0x66, 0xe1, 0xe1, 0x1e, 0x4b, 0xf9, 0xff, 0xca, 0xcf, 0x06, 0xfd, 0xec, 0x23, 0x77, 0xf3, + 0x62, 0xd0, 0xb0, 0xba, 0x6c, 0x06, 0x52, 0x3b, 0x72, 0x62, 0xa5, 0xad, 0xa0, 0xef, 0xec, 0x94, + 0xef, 0xa0, 0x2b, 0xb8, 0x00, 0xee, 0x1d, 0x98, 0xb6, 0xe8, 0x55, 0x09, 0xb7, 0x99, 0xc5, 0xc9, + 0xd6, 0xef, 0x24, 0x48, 0x56, 0xb8, 0x21, 0x9f, 0x82, 0x74, 0xd4, 0x85, 0xf1, 0xc4, 0xb7, 0x49, + 0xd1, 0xa7, 0x44, 0xc9, 0xf8, 0x60, 0x53, 0x1a, 0xf2, 0x19, 0x58, 0x8b, 0x3f, 0x3b, 0xcf, 0xc3, + 0x14, 0x22, 0xc0, 0x31, 0x3a, 0x6f, 0x81, 0x12, 0xd3, 0xb5, 0xf9, 0x30, 0x91, 0x30, 0x64, 0x8c, + 0xc2, 0x31, 0x58, 0x0a, 0xbd, 0xda, 0xe1, 0x34, 0x77, 0x18, 0x26, 0x86, 0xf5, 0x35, 0xc8, 0x44, + 0xb6, 0xe1, 0xd3, 0xb0, 0xac, 0x83, 0xb8, 0x68, 0x76, 0xe5, 0xff, 0x0f, 0x37, 0xe7, 0x1b, 0x52, + 0xf9, 0xf0, 0xe2, 0x4a, 0x95, 0x2e, 0xaf, 0x54, 0xe9, 0xe7, 0x95, 0x2a, 0x7d, 0xb9, 0x56, 0x13, + 0x97, 0xd7, 0x6a, 0xe2, 0xc7, 0xb5, 0x9a, 0x38, 0x2d, 0x18, 0x54, 0x34, 0xda, 0xf5, 0xa2, 0xce, + 0x4c, 0x4d, 0xb0, 0x77, 0xc4, 0xa2, 0xef, 0x49, 0xa1, 0xab, 0x89, 0x6e, 0x41, 0x6f, 0x20, 0x6a, + 0x69, 0x9d, 0x5d, 0xcd, 0x79, 0xf2, 0x44, 0xcf, 0x26, 0xbc, 0x7e, 0x67, 0xf4, 0xd2, 0x6d, 0xff, + 0x0b, 0x00, 0x00, 0xff, 0xff, 0xf4, 0x6e, 0x82, 0x52, 0x85, 0x07, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -362,6 +425,8 @@ type MsgClient interface { UpdateDistributionSchedule(ctx context.Context, in *MsgUpdateDistributionSchedule, opts ...grpc.CallOption) (*EmptyResponse, error) // DisableDistributions is a governance operation to disable distributions. DisableDistributions(ctx context.Context, in *MsgDisableDistributions, opts ...grpc.CallOption) (*EmptyResponse, error) + // UpdateMinDistributionGap is a governance operation to update the minimum gap between distributions. + UpdateMinDistributionGap(ctx context.Context, in *MsgUpdateMinDistributionGap, opts ...grpc.CallOption) (*EmptyResponse, error) } type msgClient struct { @@ -408,6 +473,15 @@ func (c *msgClient) DisableDistributions(ctx context.Context, in *MsgDisableDist return out, nil } +func (c *msgClient) UpdateMinDistributionGap(ctx context.Context, in *MsgUpdateMinDistributionGap, opts ...grpc.CallOption) (*EmptyResponse, error) { + out := new(EmptyResponse) + err := c.cc.Invoke(ctx, "/tx.pse.v1.Msg/UpdateMinDistributionGap", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { // UpdateExcludedAddresses is a governance operation to update the list of excluded addresses. @@ -418,6 +492,8 @@ type MsgServer interface { UpdateDistributionSchedule(context.Context, *MsgUpdateDistributionSchedule) (*EmptyResponse, error) // DisableDistributions is a governance operation to disable distributions. DisableDistributions(context.Context, *MsgDisableDistributions) (*EmptyResponse, error) + // UpdateMinDistributionGap is a governance operation to update the minimum gap between distributions. + UpdateMinDistributionGap(context.Context, *MsgUpdateMinDistributionGap) (*EmptyResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -436,6 +512,9 @@ func (*UnimplementedMsgServer) UpdateDistributionSchedule(ctx context.Context, r func (*UnimplementedMsgServer) DisableDistributions(ctx context.Context, req *MsgDisableDistributions) (*EmptyResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method DisableDistributions not implemented") } +func (*UnimplementedMsgServer) UpdateMinDistributionGap(ctx context.Context, req *MsgUpdateMinDistributionGap) (*EmptyResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateMinDistributionGap not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -513,6 +592,24 @@ func _Msg_DisableDistributions_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _Msg_UpdateMinDistributionGap_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUpdateMinDistributionGap) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).UpdateMinDistributionGap(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/tx.pse.v1.Msg/UpdateMinDistributionGap", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).UpdateMinDistributionGap(ctx, req.(*MsgUpdateMinDistributionGap)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "tx.pse.v1.Msg", HandlerType: (*MsgServer)(nil), @@ -533,6 +630,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "DisableDistributions", Handler: _Msg_DisableDistributions_Handler, }, + { + MethodName: "UpdateMinDistributionGap", + Handler: _Msg_UpdateMinDistributionGap_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "tx/pse/v1/tx.proto", @@ -704,6 +805,41 @@ func (m *MsgUpdateDistributionSchedule) MarshalToSizedBuffer(dAtA []byte) (int, return len(dAtA) - i, nil } +func (m *MsgUpdateMinDistributionGap) 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 *MsgUpdateMinDistributionGap) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateMinDistributionGap) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.MinDistributionGapSeconds != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.MinDistributionGapSeconds)) + i-- + dAtA[i] = 0x10 + } + 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 *EmptyResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -814,6 +950,22 @@ func (m *MsgUpdateDistributionSchedule) Size() (n int) { return n } +func (m *MsgUpdateMinDistributionGap) 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)) + } + if m.MinDistributionGapSeconds != 0 { + n += 1 + sovTx(uint64(m.MinDistributionGapSeconds)) + } + return n +} + func (m *EmptyResponse) Size() (n int) { if m == nil { return 0 @@ -1289,6 +1441,107 @@ func (m *MsgUpdateDistributionSchedule) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgUpdateMinDistributionGap) 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: MsgUpdateMinDistributionGap: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateMinDistributionGap: 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 != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MinDistributionGapSeconds", wireType) + } + m.MinDistributionGapSeconds = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MinDistributionGapSeconds |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + 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 *EmptyResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0