Add market fee discount to protocol#3130
Conversation
WalkthroughAdds per-market fee discount support end-to-end: proto messages/RPCs, CLI and TypeScript codegen, keeper storage/validation, msg and gRPC handlers, tests, and propagates clobPairId into fee calculations; also regenerates several generated bundle exports and updates CI triggers. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant CLI as CLI
participant QClient as Query Client
participant Keeper as FeeTiers Keeper
Note over User,Keeper: Query per-market fee discount params
User->>CLI: dydx feetiers market-fee-discount-params [clob_pair_id]
CLI->>QClient: PerMarketFeeDiscountParams(clobPairId?) / AllMarketFeeDiscountParams()
QClient->>Keeper: PerMarketFeeDiscountParams / AllMarketFeeDiscountParams
alt clob_pair_id provided
Keeper-->>QClient: params or NotFound
else no arg
Keeper-->>QClient: [params...]
end
QClient-->>CLI: response
CLI-->>User: print JSON
sequenceDiagram
autonumber
actor Gov as Gov Authority
participant TxClient as Msg Client
participant MsgSrv as feetiers.Msg
participant Keeper as FeeTiers Keeper
Note over Gov,Keeper: Set per-market fee discount params
Gov->>TxClient: MsgSetMarketFeeDiscountParams{authority, params[]}
TxClient->>MsgSrv: SetMarketFeeDiscountParams(request)
MsgSrv->>Keeper: Validate each params (time, charge)
alt any invalid
MsgSrv-->>Gov: error (ErrInvalidTimeRange / ErrInvalidChargePpm)
else all valid
MsgSrv->>Keeper: SetPerMarketFeeDiscountParams for each
MsgSrv-->>Gov: MsgSetMarketFeeDiscountParamsResponse{}
end
sequenceDiagram
autonumber
participant CLOB as CLOB Keeper
participant Feetiers as FeeTiers Keeper
Note over CLOB,Feetiers: Fee calculation with per-market discount
CLOB->>Feetiers: GetPerpetualFeePpm(ctx, address, isTaker, overrideIdx, clobPairId)
Feetiers->>Feetiers: baseFee = taker/maker tier
Feetiers->>Feetiers: discountPpm = GetDiscountedPpm(clobPairId)
Feetiers-->>CLOB: discountedFee = baseFee * discountPpm / MaxChargePpm
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
protocol/x/feetiers/types/fee_discount_campaign.go (1)
8-8: Minor terminology inconsistency in comment.The comment refers to "fee holiday" but the feature is called "fee discount campaign" throughout the codebase. Consider updating the comment for consistency.
- // Maximum duration for a fee holiday (90 days in seconds) + // Maximum duration for a fee discount campaign (90 days in seconds)proto/dydxprotocol/feetiers/fee_discount_campaign.proto (1)
1-22: Add trailing newline for consistency.The proto definition is well-structured with clear documentation. However, the file is missing a trailing newline.
Add a newline at the end of the file:
uint32 charge_ppm = 4; } +indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/query.ts (1)
102-102: Consider type aliases for empty interfaces (codegen improvement).Static analysis correctly flags that
QueryAllFeeDiscountCampaignParamsRequestand its SDK type are empty interfaces equivalent to{}. While this is standard for empty protobuf messages, using type aliases would be more idiomatic TypeScript:export type QueryAllFeeDiscountCampaignParamsRequest = {}; export type QueryAllFeeDiscountCampaignParamsRequestSDKType = {};However, since this is generated code, the fix would need to be implemented in the code generator itself.
Also applies to: 108-108
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (4)
protocol/x/feetiers/types/fee_discount_campaign.pb.gois excluded by!**/*.pb.goprotocol/x/feetiers/types/query.pb.gois excluded by!**/*.pb.goprotocol/x/feetiers/types/query.pb.gw.gois excluded by!**/*.pb.gw.goprotocol/x/feetiers/types/tx.pb.gois excluded by!**/*.pb.go
📒 Files selected for processing (26)
indexer/packages/v4-protos/src/codegen/dydxprotocol/bundle.ts(3 hunks)indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/fee_discount_campaign.ts(1 hunks)indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/query.lcd.ts(3 hunks)indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/query.rpc.Query.ts(5 hunks)indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/query.ts(3 hunks)indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/tx.rpc.msg.ts(2 hunks)indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/tx.ts(3 hunks)indexer/packages/v4-protos/src/codegen/gogoproto/bundle.ts(1 hunks)indexer/packages/v4-protos/src/codegen/google/bundle.ts(1 hunks)proto/dydxprotocol/feetiers/fee_discount_campaign.proto(1 hunks)proto/dydxprotocol/feetiers/query.proto(3 hunks)proto/dydxprotocol/feetiers/tx.proto(2 hunks)protocol/app/msgs/all_msgs.go(1 hunks)protocol/app/msgs/internal_msgs.go(1 hunks)protocol/app/msgs/internal_msgs_test.go(1 hunks)protocol/lib/ante/internal_msg.go(1 hunks)protocol/x/feetiers/keeper/fee_discount_campaign.go(1 hunks)protocol/x/feetiers/keeper/fee_discount_campaign_test.go(1 hunks)protocol/x/feetiers/keeper/grpc_query.go(2 hunks)protocol/x/feetiers/keeper/grpc_query_test.go(2 hunks)protocol/x/feetiers/keeper/msg_server.go(1 hunks)protocol/x/feetiers/keeper/msg_server_test.go(2 hunks)protocol/x/feetiers/types/errors.go(1 hunks)protocol/x/feetiers/types/fee_discount_campaign.go(1 hunks)protocol/x/feetiers/types/keys.go(1 hunks)protocol/x/feetiers/types/types.go(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (14)
protocol/x/feetiers/keeper/grpc_query_test.go (2)
protocol/testutil/app/app.go (1)
NewTestAppBuilder(335-350)protocol/app/app.go (1)
App(271-376)
protocol/x/feetiers/keeper/msg_server.go (3)
protocol/x/feetiers/keeper/keeper.go (1)
Keeper(18-26)protocol/lib/context.go (1)
UnwrapSDKContext(31-55)protocol/x/feetiers/types/keys.go (1)
ModuleName(6-6)
protocol/x/feetiers/keeper/fee_discount_campaign_test.go (4)
protocol/testutil/app/app.go (1)
NewTestAppBuilder(335-350)protocol/app/app.go (1)
App(271-376)protocol/x/feetiers/types/errors.go (1)
ErrFeeDiscountCampaignNotFound(43-47)protocol/x/feetiers/types/fee_discount_campaign.go (1)
MaxChargePpm(12-12)
protocol/x/feetiers/keeper/grpc_query.go (3)
protocol/lib/context.go (1)
UnwrapSDKContext(31-55)protocol/x/feetiers/types/keys.go (1)
ModuleName(6-6)protocol/x/feetiers/types/errors.go (1)
ErrFeeDiscountCampaignNotFound(43-47)
protocol/x/feetiers/types/fee_discount_campaign.go (1)
protocol/x/feetiers/types/errors.go (2)
ErrInvalidTimeRange(38-42)ErrInvalidChargePpm(48-52)
indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/query.lcd.ts (1)
indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/query.ts (6)
QueryFeeDiscountCampaignParamsRequest(62-68)QueryFeeDiscountCampaignParamsRequest(311-348)QueryFeeDiscountCampaignParamsResponseSDKType(94-96)QueryAllFeeDiscountCampaignParamsRequest(102-102)QueryAllFeeDiscountCampaignParamsRequest(399-427)QueryAllFeeDiscountCampaignParamsResponseSDKType(122-124)
indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/tx.rpc.msg.ts (1)
indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/tx.ts (4)
MsgSetFeeDiscountCampaignParams(38-44)MsgSetFeeDiscountCampaignParams(166-212)MsgSetFeeDiscountCampaignParamsResponse(62-62)MsgSetFeeDiscountCampaignParamsResponse(218-246)
indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/query.ts (1)
indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/fee_discount_campaign.ts (3)
FeeDiscountCampaignParams(8-24)FeeDiscountCampaignParams(57-121)FeeDiscountCampaignParamsSDKType(30-46)
protocol/x/feetiers/types/errors.go (1)
protocol/x/feetiers/types/keys.go (1)
ModuleName(6-6)
indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/tx.ts (1)
indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/fee_discount_campaign.ts (3)
FeeDiscountCampaignParams(8-24)FeeDiscountCampaignParams(57-121)FeeDiscountCampaignParamsSDKType(30-46)
indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/query.rpc.Query.ts (1)
indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/query.ts (8)
QueryFeeDiscountCampaignParamsRequest(62-68)QueryFeeDiscountCampaignParamsRequest(311-348)QueryFeeDiscountCampaignParamsResponse(86-88)QueryFeeDiscountCampaignParamsResponse(356-393)QueryAllFeeDiscountCampaignParamsRequest(102-102)QueryAllFeeDiscountCampaignParamsRequest(399-427)QueryAllFeeDiscountCampaignParamsResponse(114-116)QueryAllFeeDiscountCampaignParamsResponse(435-472)
protocol/x/feetiers/keeper/msg_server_test.go (1)
protocol/lib/module_addresses.go (1)
GovModuleAddress(10-10)
protocol/x/feetiers/keeper/fee_discount_campaign.go (4)
protocol/x/feetiers/types/keys.go (1)
FeeDiscountCampaignPrefix(18-18)protocol/lib/keys.go (1)
Uint32ToKey(9-13)protocol/x/feetiers/types/errors.go (1)
ErrFeeDiscountCampaignNotFound(43-47)protocol/x/feetiers/types/fee_discount_campaign.go (1)
MaxChargePpm(12-12)
indexer/packages/v4-protos/src/codegen/dydxprotocol/bundle.ts (2)
indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/query.lcd.ts (1)
vault(31-34)indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/query.rpc.Query.ts (2)
vault(53-57)vault(108-110)
🪛 Biome (2.1.2)
indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/query.ts
[error] 102-102: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
[error] 108-108: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/tx.ts
[error] 62-62: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
[error] 68-68: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (38)
- GitHub Check: (Mainnet) Build and Push ECS Services / call-build-and-push-auxo-lambda / (auxo) Build and Push Lambda
- GitHub Check: (Mainnet) Build and Push ECS Services / call-build-and-push-bazooka-lambda / (bazooka) Build and Push Lambda
- GitHub Check: (Mainnet) Build and Push ECS Services / call-build-and-push-vulcan / (vulcan) Build and Push
- GitHub Check: (Public Testnet) Build and Push ECS Services / call-build-and-push-bazooka-lambda / (bazooka) Build and Push Lambda
- GitHub Check: (Public Testnet) Build and Push ECS Services / call-build-and-push-auxo-lambda / (auxo) Build and Push Lambda
- GitHub Check: (Public Testnet) Build and Push ECS Services / call-build-and-push-ecs-service-socks / (socks) Build and Push
- GitHub Check: (Public Testnet) Build and Push ECS Services / call-build-and-push-vulcan / (vulcan) Build and Push
- GitHub Check: (Mainnet) Build and Push ECS Services / call-build-and-push-ecs-service-roundtable / (roundtable) Build and Push
- GitHub Check: (Public Testnet) Build and Push ECS Services / call-build-and-push-ecs-service-roundtable / (roundtable) Build and Push
- GitHub Check: (Public Testnet) Build and Push ECS Services / call-build-and-push-ecs-service-ender / (ender) Build and Push
- GitHub Check: (Public Testnet) Build and Push ECS Services / call-build-and-push-ecs-service-comlink / (comlink) Build and Push
- GitHub Check: (Mainnet) Build and Push ECS Services / call-build-and-push-ecs-service-socks / (socks) Build and Push
- GitHub Check: (Mainnet) Build and Push ECS Services / call-build-and-push-ecs-service-ender / (ender) Build and Push
- GitHub Check: (Mainnet) Build and Push ECS Services / call-build-and-push-ecs-service-comlink / (comlink) Build and Push
- GitHub Check: call-build-ecs-service-ender / (ender) Check docker image build
- GitHub Check: call-build-ecs-service-socks / (socks) Check docker image build
- GitHub Check: call-build-ecs-service-vulcan / (vulcan) Check docker image build
- GitHub Check: call-build-ecs-service-roundtable / (roundtable) Check docker image build
- GitHub Check: call-build-ecs-service-comlink / (comlink) Check docker image build
- GitHub Check: check-build-auxo
- GitHub Check: check-build-bazooka
- GitHub Check: test / run_command
- GitHub Check: container-tests
- GitHub Check: build-and-push-testnet
- GitHub Check: build
- GitHub Check: benchmark
- GitHub Check: lint
- GitHub Check: check-sample-pregenesis-up-to-date
- GitHub Check: test-coverage-upload
- GitHub Check: test-race
- GitHub Check: liveness-test
- GitHub Check: unit-end-to-end-and-integration
- GitHub Check: build-and-push-mainnet
- GitHub Check: golangci-lint
- GitHub Check: run_command
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: Analyze (go)
- GitHub Check: Summary
🔇 Additional comments (38)
indexer/packages/v4-protos/src/codegen/google/bundle.ts (1)
1-16: LGTM! Mechanical codegen reorganization.The import renumbering and bundle reorganization are standard protobuf codegen output. The addition of
protobuf/anymodule and the renumbered spreads maintain the same public API surface.indexer/packages/v4-protos/src/codegen/gogoproto/bundle.ts (1)
1-3: LGTM! Mechanical codegen reorganization.The import renumbering is standard protobuf codegen output with no functional change.
protocol/lib/ante/internal_msg.go (1)
100-100: LGTM! Correct internal message registration.The new
MsgSetFeeDiscountCampaignParamstype is properly added to the internal message type switch, consistent with existing patterns and correctly placed in the feetiers section.protocol/x/feetiers/types/keys.go (1)
16-18: LGTM! Well-defined store key prefix.The
FeeDiscountCampaignPrefixconstant is properly defined with clear documentation for storing fee discount campaign data.protocol/app/msgs/internal_msgs_test.go (1)
115-116: LGTM! Test expectations properly updated.The new feetiers message keys are correctly added to the Gov message expectations, maintaining alphabetical order and including both request and response types.
indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/tx.ts (3)
2-2: LGTM! Correct import for new campaign params.The import properly references the new
FeeDiscountCampaignParamstypes from the fee_discount_campaign module.
33-68: LGTM! Standard protobuf message definitions.The message interfaces follow the established patterns for protobuf codegen. The empty response interfaces (lines 62, 68) are standard for protobuf messages with no response fields—this is expected codegen output, not an issue.
159-246: LGTM! Correct encode/decode implementation.The encode, decode, and fromPartial methods properly handle the
paramsarray field:
- Line 172-174: Correctly iterates and encodes each FeeDiscountCampaignParams
- Line 193: Properly decodes and pushes to params array
- Line 208: Correctly maps partial objects to FeeDiscountCampaignParams instances
protocol/app/msgs/all_msgs.go (1)
221-222: LGTM! Message types properly registered.The new feetiers message types are correctly added to the
AllTypeMessagesregistry, consistent with existing patterns and properly placed in the feetiers section.protocol/x/feetiers/keeper/grpc_query_test.go (4)
5-5: LGTM! Required import for time-based tests.The
timepackage import is necessary for setting block time in the fee discount campaign tests.
102-160: LGTM! Comprehensive test coverage for FeeDiscountCampaignParams query.The test properly covers:
- Success case with valid campaign retrieval
- Nil request handling with proper error code
- Not Found case for non-existent CLOB pair
- Block time set before campaign creation (line 118) for time-based validation
162-246: LGTM! Excellent handling of unordered results.The test demonstrates best practices:
- Sets up multiple campaigns with varying discount levels (0%, 50%, 75%)
- Uses map-based comparison (lines 229-242) to handle unordered query results
- Verifies all campaign fields (ClobPairId, StartTimeUnix, EndTimeUnix, ChargePpm)
- Includes nil request test case
248-263: LGTM! Proper empty state test coverage.The test correctly verifies that querying with no campaigns returns an empty list rather than an error, which is the expected behavior for a "list all" query.
protocol/x/feetiers/keeper/msg_server.go (1)
45-82: LGTM! Well-structured governance handler.The
SetFeeDiscountCampaignParamshandler follows the established pattern fromUpdatePerpetualFeeParamsand correctly:
- Validates authority before processing
- Unwraps SDK context with module name for logging
- Validates each campaign against the current block time before persistence
- Uses fail-fast pattern by returning on first validation or persistence error
- Wraps errors with contextual information including the CLOB pair ID
protocol/x/feetiers/types/types.go (1)
18-25: LGTM! Clean interface extension.The two new methods are well-defined and follow the existing interface conventions. The naming is clear and the signatures are consistent with similar methods in the keeper.
protocol/app/msgs/internal_msgs.go (1)
161-162: LGTM! Standard message registration.The new feetiers messages are correctly registered following the established pattern for governance-authorized internal messages.
protocol/x/feetiers/keeper/msg_server_test.go (2)
85-225: LGTM! Comprehensive test coverage.The test suite covers all critical scenarios:
- Valid configurations (single/multiple campaigns)
- Authority validation
- Time range validations (end before current, start after end, excessive duration)
- ChargePpm validation (exceeds maximum)
- Empty params handling
The use of fixed base time (line 90-91) ensures deterministic behavior.
227-333: LGTM! Update and multi-campaign tests are thorough.These tests properly verify:
- Updates to existing campaigns overwrite correctly
- Multiple campaigns can be set simultaneously
- GetAllFeeDiscountCampaignParams returns all configured campaigns
protocol/x/feetiers/types/fee_discount_campaign.go (1)
15-39: LGTM! Validation logic is comprehensive and correct.The validation properly checks:
- Time range ordering (start < end)
- Maximum duration constraint
- ChargePpm bounds
- Campaign hasn't already expired
protocol/x/feetiers/keeper/fee_discount_campaign_test.go (1)
12-277: LGTM! Excellent test coverage for fee discount campaign functionality.This test suite is comprehensive and covers:
- Basic CRUD operations (Get/Set/GetAll)
- Error handling (
ErrFeeDiscountCampaignNotFound)- Time-based discount eligibility across all edge cases (before start, at start, between, at end, after end)
- Campaign updates and overwrites
- Empty state handling (verifies non-nil empty slice)
The use of fixed base times throughout ensures deterministic, reproducible tests.
protocol/x/feetiers/types/errors.go (1)
38-52: LGTM! Error definitions are clear and properly registered.The three new error codes (406-408) are:
- Sequentially numbered without conflicts
- Clearly named and descriptive
- Properly registered using
errorsmod.Registerprotocol/x/feetiers/keeper/grpc_query.go (3)
5-5: LGTM! Correct import for error inspection.The
errorsimport is necessary for theerrors.Ischeck on line 75.
59-84: LGTM! Well-implemented gRPC query handler.The
FeeDiscountCampaignParamshandler correctly:
- Validates nil request
- Unwraps SDK context with module name
- Maps
ErrFeeDiscountCampaignNotFoundtocodes.NotFoundfor proper gRPC semantics- Returns
codes.Internalfor unexpected errors with context
86-104: LGTM! Clean implementation for querying all campaigns.The
AllFeeDiscountCampaignParamshandler follows the established pattern and correctly handles the request-response flow.indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/query.lcd.ts (1)
2-2: LGTM! Generated LCD client code follows established patterns.The implementation correctly adds LCD query client methods for fee discount campaigns:
- Import statement includes all necessary types
- Constructor properly binds the new methods
feeDiscountCampaignParamsuses path parameter interpolation forclobPairIdallFeeDiscountCampaignParamsqueries all campaigns without parametersThe code follows the same pattern as existing query methods (
perpetualFeeParams,userFeeTier).Also applies to: 14-15, 39-53
proto/dydxprotocol/feetiers/tx.proto (1)
9-9: LGTM! New RPC and message types follow Cosmos SDK patterns.The additions are well-structured:
- Import statement for the new proto definition
SetFeeDiscountCampaignParamsRPC follows the same pattern asUpdatePerpetualFeeParamsMsgSetFeeDiscountCampaignParamscorrectly includes authority field with signer optionrepeated FeeDiscountCampaignParamswithgogoproto.nullable = falseis appropriate for batch operations- Empty response message follows standard practice
Also applies to: 17-53
indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/query.ts (2)
2-2: LGTM! Generated query type definitions are correct.The new query types are properly structured:
- Import statement includes the new
FeeDiscountCampaignParamstypes- Four new interfaces (Request/Response pairs with SDK types) follow protobuf conventions
- Field types are correct (
clobPairId: number, optionalparams, array ofparams)Also applies to: 57-124
305-472: LGTM! Encode/decode implementations are correct.The protobuf encode/decode/fromPartial implementations for all four query types follow standard patterns:
- Proper handling of default values (0 for numbers, undefined for optional objects)
- Correct protobuf wire types and tags
- Array handling in
QueryAllFeeDiscountCampaignParamsResponse- Delegation to
FeeDiscountCampaignParamscodec for nested messagesindexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/tx.rpc.msg.ts (1)
3-3: LGTM! Generated RPC client implementation follows established patterns.The new
setFeeDiscountCampaignParamsmethod is correctly implemented:
- Import includes both request and response types
- Interface method signature is properly typed with
Promisereturn- Constructor binding ensures correct
thiscontext- Implementation follows standard pattern: encode → RPC request → decode response
Also applies to: 9-14, 22-22, 31-35
protocol/x/feetiers/keeper/fee_discount_campaign.go (4)
14-30: LGTM! GetFeeDiscountCampaignParams is correctly implemented.The method properly:
- Uses prefix store with
FeeDiscountCampaignPrefix- Returns
ErrFeeDiscountCampaignNotFoundwhen campaign doesn't exist- Handles unmarshal errors appropriately
- Returns the params on success
33-52: LGTM! SetFeeDiscountCampaignParams includes proper validation.The method correctly:
- Validates params before storage using
params.Validate(ctx.BlockTime())- Uses prefix store with the correct key (
lib.Uint32ToKey(params.ClobPairId))- Marshals params with the codec
- Handles marshal errors
The validation ensures that invalid campaigns (expired, invalid time ranges, invalid charge_ppm) cannot be stored.
55-79: LGTM! GetAllFeeDiscountCampaignParams handles errors gracefully.The method is well-implemented:
- Properly closes iterator with
defer iterator.Close()- Handles unmarshal errors by logging and continuing (robust against corrupted entries)
- Decodes
clobPairIdfrom key for error logging usingbinary.BigEndian.Uint32(matcheslib.Uint32ToKey)- Returns empty slice when no campaigns exist
The error handling approach (log and skip) is appropriate for enumeration methods, preventing one corrupted entry from breaking the entire query.
84-108: LGTM! GetDiscountPpm implements correct active campaign logic.The method correctly:
- Retrieves campaign params for the CLOB pair
- Treats
ErrFeeDiscountCampaignNotFoundas a normal case (no logging)- Logs unexpected errors for visibility
- Checks if current time is within the campaign window using
currentTime >= campaign.StartTimeUnix && currentTime < campaign.EndTimeUnix
- Start time is inclusive
- End time is exclusive
- This is correct for time range semantics
- Returns
campaign.ChargePpmwhen active,MaxChargePpmotherwiseThe fallback to
MaxChargePpm(100% charge, no discount) is appropriate for both missing campaigns and inactive campaigns.indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/fee_discount_campaign.ts (1)
1-121: LGTM! Generated FeeDiscountCampaignParams types are well-formed.The generated code is correct:
- Interface definitions properly use
Longfor 64-bit timestamp fields (startTimeUnix,endTimeUnix)- SDK type uses snake_case field names (
clob_pair_id,start_time_unix, etc.)- Default values are appropriate:
0for numbers,Long.ZEROfor int64- Encode writes fields with correct protobuf tags (1-4) and wire types
- Decode handles all field tags correctly with type casting for Long fields
fromPartialproperly converts values toLongwhen present, using defaults otherwiseproto/dydxprotocol/feetiers/query.proto (2)
8-8: LGTM! Query RPC definitions follow Cosmos SDK patterns.The additions are well-structured:
- Import statement for
fee_discount_campaign.proto- Two new RPC methods with proper documentation
- HTTP GET endpoints correctly defined:
/dydxprotocol/feetiers/fee_discount_campaign_params/{clob_pair_id}for single campaign query/dydxprotocol/feetiers/fee_discount_campaign_paramsfor listing all campaigns- Follows the same pattern as existing
PerpetualFeeParamsandUserFeeTierRPCsAlso applies to: 25-39
64-83: LGTM! Message definitions are correctly structured.The query message types are properly defined:
QueryFeeDiscountCampaignParamsRequestwithclob_pair_idfieldQueryFeeDiscountCampaignParamsResponsewith non-nullableparamsfieldQueryAllFeeDiscountCampaignParamsRequestis empty (standard for list-all queries)QueryAllFeeDiscountCampaignParamsResponsewith repeated non-nullableparamsThe use of
gogoproto.nullable = falseis consistent with other messages in the module and ensures fields are always present.indexer/packages/v4-protos/src/codegen/dydxprotocol/feetiers/query.rpc.Query.ts (1)
4-4: LGTM! Clean RPC query client implementation.The fee discount campaign query methods are correctly implemented following the established patterns:
- Proper type imports and interface extensions
- Constructor bindings for method context
- Standard encode-request → RPC-call → decode-response flow
- Correct delegation in the extension factory
The
allFeeDiscountCampaignParamsmethod appropriately defaults the request parameter to an empty object, consistent with similar query methods.Also applies to: 13-21, 30-31, 46-56, 69-77
indexer/packages/v4-protos/src/codegen/dydxprotocol/bundle.ts (1)
53-61: LGTM! Consistent mechanical refactoring.The bundle correctly incorporates the new fee discount campaign module:
- New import
_57forfee_discount_campaignadded at line 57- All subsequent imports systematically shifted by one index
- The
feetiersnamespace properly includes the new module and updated indices for query/tx handlersThis is standard codegen output with consistent index management across all namespaces.
Also applies to: 276-284
|
@Mergifyio backport release/protocol/v9.x |
✅ Backports have been createdDetails
|
|
@Mergifyio backport release/indexer/v9.x |
✅ Backports have been createdDetails
|
|
https://github.com/Mergifyio backport release/protocol/v9.x |
✅ Backports have been createdDetails
|
Co-authored-by: David Li <[email protected]>
|
https://github.com/Mergifyio backport release/indexer/v9.x |
✅ Backports have been createdDetails
|
|
https://github.com/Mergifyio backport release/indexer/v9.4.x-2 |
✅ Backports have been createdDetails
|
(cherry picked from commit 33a4abb) # Conflicts: # protocol/x/feetiers/client/cli/query.go # protocol/x/feetiers/keeper/grpc_query.go # protocol/x/feetiers/keeper/grpc_query_test.go # protocol/x/feetiers/keeper/keeper.go # protocol/x/feetiers/keeper/keeper_test.go
Changelist
This PR implements a Market Fee Discount feature that allows governance to configure flexible fee discount periods for specific markets/CLOB pairs. During an active discount period, fees can be reduced by any percentage (from 0% to 100% of normal fees) for designated markets during specified time periods.
Detailed tech spec
Key Changes:
PerMarketFeeDiscountParamsmessage withcharge_ppmfield for flexible discount percentagesMsgSetMarketFeeDiscountParams) with validation for time ranges, CLOB pair existence, and charge_ppm limitsGetPerpetualFeePpm()now checks for active fee discounts and applies proportional discount based oncharge_ppmclobPairIdto fee calculation functionBreaking Changes:
GetPerpetualFeePpm()signature updated to includeclobPairIdparameter (all callers updated in this PR)Example Governance Proposal
Test Plan
Unit Tests
Manual Testing on Staging
Fill Result (BEFORE discount):
Author/Reviewer Checklist
state-breakinglabel.indexer-postgres-breakinglabel.PrepareProposalorProcessProposal, manually add the labelproposal-breaking.feature:[feature-name].backport/[branch-name].refactor,chore,bug.Summary by CodeRabbit
New Features
Improvements
Tests
Chores