[chore] release for v1.20.1#369
Conversation
New Features - Rate limit settings now support additional oracle-based pricing options via new oracle type fields. - Rate limit creation and update flows now include oracle type selection. Bug Fixes - Tightened authorization handling to consistently validate spending against contextual holdings and enforce exhausted-grant rules. - Improved canceled derivative order refund calculations for edge cases and adjusted fee/margin refund behavior.
New Features - Added support for additional Cardano light-client data types, improving interoperability with related network messages. Bug Fixes - Improved protobuf interface registration so supported transaction and asset types are handled more reliably. - Updated dependency versions to include the latest compatible fixes and enhancements.
📝 WalkthroughWalkthroughRemoves Hyperlane dependency and replaces it with Cardano probabilistic light client in ChangesHyperlane → Cardano Dependency Swap and Proto Codec Wiring
Peggy Rate-Limit Oracle Type Fields
Exchange Authz and Derivative Order Fixes
Build and Repository Config
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
chain/exchange/types/v2/authz_exchange_generic.go (1)
113-127: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftDo not use
SpendLimit.IsZero()to mean exhausted.
GenericExchangeAuthorization.SpendLimitis documented as “if not set, there is no spend limit,” so an empty limit still needs to allow non-zero holds. This check blocks unlimited grants and also makes exhausted vs. unlimited indistinguishable after updates; store that state explicitly instead of overloading emptyCoins.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@chain/exchange/types/v2/authz_exchange_generic.go` around lines 113 - 127, The hold-check in GenericExchangeAuthorization currently treats SpendLimit.IsZero() as “exhausted,” which incorrectly blocks unlimited grants and conflates empty with depleted state. Update the authz evaluation logic in the authorization path around the spend-limit validation to track exhaustion explicitly instead of using an empty Coins value as the signal. Preserve unlimited grants by allowing non-zero holds when no spend limit is set, and ensure Updated/NewGenericExchangeAuthorization still round-trips the actual state without ambiguity.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@chain/exchange/types/authz_exchange_generic.go`:
- Around line 121-135: The grant handling in authz_exchange_generic.go is
collapsing unlimited and exhausted spend limits because a.SpendLimit.IsZero()
treats nil/unset and empty/zero the same. Update the logic around the hold check
and the Updated authz creation to preserve the unlimited-grant representation
separately from an exhausted limit, so valid unlimited grants are not rejected
and the updated authorization does not serialize back as a zero value. Use the
existing symbols a.SpendLimit, hold, and NewGenericExchangeAuthorization to keep
the distinction intact before returning authz.AcceptResponse.
In `@client/chain/context.go`:
- Around line 186-187: The duplicate EVM/ERC20 interface registrations in
createInjectiveProtoCodec() should be removed from the extra registration block
in context.go, since those modules are already registered earlier in the same
codec setup. Keep the unique registrations only once by relying on the existing
createInjectiveProtoCodec() setup and delete the repeated
evmtypes.RegisterInterfaces and erc20types.RegisterInterfaces calls to avoid
re-registering the Msg service descriptors and triggering codec initialization
panics.
In `@proto/injective/peggy/v1/msgs.proto`:
- Around line 438-439: The comment for MsgUpdateRateLimit still references
token_price_id, which is misleading because the message only defines
new_token_price_id. Update the surrounding comment near the
new_token_oracle_type/new_token_price_id fields in the protobuf definition so
the docs and generated API text point to new_token_price_id consistently.
---
Outside diff comments:
In `@chain/exchange/types/v2/authz_exchange_generic.go`:
- Around line 113-127: The hold-check in GenericExchangeAuthorization currently
treats SpendLimit.IsZero() as “exhausted,” which incorrectly blocks unlimited
grants and conflates empty with depleted state. Update the authz evaluation
logic in the authorization path around the spend-limit validation to track
exhaustion explicitly instead of using an empty Coins value as the signal.
Preserve unlimited grants by allowing non-zero holds when no spend limit is set,
and ensure Updated/NewGenericExchangeAuthorization still round-trips the actual
state without ambiguity.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 386ba4b6-13ba-4fdb-a270-273b78ce3032
⛔ Files ignored due to path filters (5)
chain/peggy/types/msgs.pb.gois excluded by!**/*.pb.gochain/peggy/types/rate_limit.pb.gois excluded by!**/*.pb.goexchange/exchange_rpc/pb/goadesign_goagen_injective_exchange_rpc.pb.gois excluded by!**/*.pb.goexchange/exchange_rpc/pb/goadesign_goagen_injective_exchange_rpc_grpc.pb.gois excluded by!**/*.pb.gogo.sumis excluded by!**/*.sum
📒 Files selected for processing (9)
.gitignoreMakefilechain/exchange/types/authz_exchange_generic.gochain/exchange/types/v2/authz_exchange_generic.gochain/exchange/types/v2/derivative_orders.goclient/chain/context.gogo.modproto/injective/peggy/v1/msgs.protoproto/injective/peggy/v1/rate_limit.proto
| if a.SpendLimit.IsZero() && !hold.IsZero() { // on an exhausted grant only allow zero hold messages (like cancellations) (BB-225) | ||
| return authz.AcceptResponse{Accept: false}, nil | ||
| } | ||
|
|
||
| limit := a.SpendLimit | ||
| for _, coin := range hold { | ||
| allowance := limit.AmountOf(coin.Denom) | ||
| if allowance.LT(coin.Amount) { | ||
| return authz.AcceptResponse{Accept: false}, nil | ||
| } | ||
|
|
||
| for _, coin := range hold { | ||
| allowed := a.SpendLimit.AmountOf(coin.Denom) | ||
| if allowed.LT(coin.Amount) { | ||
| return authz.AcceptResponse{Accept: false}, nil | ||
| } | ||
| a.SpendLimit = a.SpendLimit.Sub(sdk.NewCoin(coin.Denom, coin.Amount)) | ||
| } | ||
| limit = limit.Sub(sdk.NewCoin(coin.Denom, coin.Amount)) | ||
| } | ||
| return authz.AcceptResponse{Accept: true, Updated: &a}, nil | ||
|
|
||
| return authz.AcceptResponse{Accept: true, Updated: NewGenericExchangeAuthorization(a.Msg, limit)}, nil |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify spend-limit representations and persistence/update behavior around GenericExchangeAuthorization.
rg -n -C4 'SpendLimit.*no spend limit|func \(a GenericExchangeAuthorization\) Accept|NewGenericExchangeAuthorization|AcceptResponse|SpendLimit\.IsZero' chain/exchange/typesRepository: InjectiveLabs/sdk-go
Length of output: 46674
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== authz_exchange_generic.go ==\n'
sed -n '1,220p' chain/exchange/types/authz_exchange_generic.go | cat -n
printf '\n== authz.pb.go references ==\n'
rg -n -C3 'SpendLimit|no spend limit|GenericExchangeAuthorization' chain/exchange/types/authz.pb.go chain/exchange/types/*.go chain/exchange/types/v2/*.go
printf '\n== getHold implementation ==\n'
sed -n '138,210p' chain/exchange/types/authz_exchange_generic.go | cat -nRepository: InjectiveLabs/sdk-go
Length of output: 47698
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== ContextKeyHold usage ==\n'
rg -n -C3 'ContextKeyHold|getHold\(' chain . | head -n 200
printf '\n== sdk.Coins IsZero semantics in module cache (if present) ==\n'
python3 - <<'PY'
import os, glob, re, sys
modcache = os.popen('go env GOMODCACHE').read().strip()
print(modcache)
matches = glob.glob(os.path.join(modcache, '**', 'coin.go'), recursive=True)
for path in matches[:20]:
try:
text = open(path, encoding='utf-8', errors='ignore').read()
except Exception:
continue
if 'type Coins' in text and 'func (coins Coins) IsZero()' in text:
print('FOUND:', path)
for m in re.finditer(r'func \(coins Coins\) IsZero\(\) bool \{', text):
start = m.start()
print(text[start:start+300])
sys.exit(0)
print('not found')
PYRepository: InjectiveLabs/sdk-go
Length of output: 8972
🏁 Script executed:
#!/bin/bash
set -euo pipefail
MODCACHE="$(go env GOMODCACHE)"
COIN_FILE="$MODCACHE/github.com/!injective!labs/cosmos-sdk@v0.50.14-inj.11/types/coin.go"
printf '\n== Coins methods ==\n'
rg -n -C4 'func \(coins Coins\) (IsZero|Sub|AmountOf|IsAllPositive|IsAnyNegative|Add|NewCoins)' "$COIN_FILE"
printf '\n== relevant sections ==\n'
sed -n '1,260p' "$COIN_FILE" | cat -nRepository: InjectiveLabs/sdk-go
Length of output: 10689
Preserve the unlimited-grant representation. sdk.Coins.IsZero() collapses nil/unset and empty/exhausted limits, so this rejects valid unlimited grants with any non-zero hold and then writes the zero value back out again. Keep exhausted grants distinguishable from “no spend limit” before persisting the updated authz.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@chain/exchange/types/authz_exchange_generic.go` around lines 121 - 135, The
grant handling in authz_exchange_generic.go is collapsing unlimited and
exhausted spend limits because a.SpendLimit.IsZero() treats nil/unset and
empty/zero the same. Update the logic around the hold check and the Updated
authz creation to preserve the unlimited-grant representation separately from an
exhausted limit, so valid unlimited grants are not rejected and the updated
authorization does not serialize back as a zero value. Use the existing symbols
a.SpendLimit, hold, and NewGenericExchangeAuthorization to keep the distinction
intact before returning authz.AcceptResponse.
| evmtypes.RegisterInterfaces(interfaceRegistry) | ||
| erc20types.RegisterInterfaces(interfaceRegistry) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## context.go (relevant slice)\n'
nl -ba client/chain/context.go | sed -n '140,210p'
printf '\n## erc20 codec\n'
nl -ba chain/erc20/types/codec.go | sed -n '1,120p'
printf '\n## evm codec\n'
fd -a 'codec.go' client chain | sed -n '1,20p' || true
rg -n "func RegisterInterfaces|RegisterInterfaces\(interfaceRegistry\)|RegisterInterfaces\(ir\)" client chain -g '*.go'Repository: InjectiveLabs/sdk-go
Length of output: 230
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## client/chain/context.go (lines 140-210)\n'
sed -n '140,210p' client/chain/context.go | awk '{printf "%4d %s\n", NR+139, $0}'
printf '\n## chain/erc20/types/codec.go\n'
sed -n '1,140p' chain/erc20/types/codec.go | awk '{printf "%4d %s\n", NR, $0}'
printf '\n## registration implementations\n'
rg -n "func RegisterInterfaces|RegisterInterfaces\(interfaceRegistry\)" client chain -g '*.go'Repository: InjectiveLabs/sdk-go
Length of output: 10504
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## chain/evm/types/codec.go\n'
sed -n '1,140p' chain/evm/types/codec.go | awk '{printf "%4d %s\n", NR, $0}'
printf '\n## chain/codec/codec.go\n'
sed -n '1,120p' chain/codec/codec.go | awk '{printf "%4d %s\n", NR, $0}'
printf '\n## any other msgservice registrations in client/chain/context.go dependencies\n'
rg -n "msgservice\.RegisterMsgServiceDesc|RegisterImplementations\(\(\*sdk\.Msg\)\(nil\)" chain client -g '*.go'Repository: InjectiveLabs/sdk-go
Length of output: 6967
🌐 Web query:
Cosmos SDK InterfaceRegistry RegisterImplementations duplicate registration panic msgservice.RegisterMsgServiceDesc same registry
💡 Result:
In the Cosmos SDK, a panic related to duplicate registration in InterfaceRegistry or RegisterMsgServiceDesc typically indicates that two different concrete types are attempting to register under the same TypeURL, or that a service/interface is being improperly registered multiple times [1][2][3]. InterfaceRegistry and Duplicate Registrations The InterfaceRegistry is designed to associate interfaces with their concrete implementations [4]. The RegisterImplementations method will trigger a panic if you attempt to register a new concrete type under a TypeURL that has already been registered with a different concrete type [1]. It is generally permitted to register the same concrete type multiple times, but registering different types for the same URL is considered a configuration error and will cause a panic to prevent ambiguity [1][2]. msgservice.RegisterMsgServiceDesc and Panics The msgservice.RegisterMsgServiceDesc function iterates through gRPC service descriptions and registers the request and response types into the InterfaceRegistry [5]. Common causes for panics in this flow include: 1. Duplicate Registrations: If RegisterMsgServiceDesc is called multiple times for the same service or if the underlying types have already been registered, it may conflict with existing entries in the InterfaceRegistry [2][3]. 2. Missing Registrations: If a service is registered via the MsgServiceRouter before its underlying types are registered in the InterfaceRegistry, the application will panic with a message stating that the TypeURL has not been registered yet [3][6]. 3. Code Generation/Configuration Issues: Panics can occur if there are discrepancies between generated Go types and the proto definitions [7]. Troubleshooting Steps - Verify Module Initialization: Ensure that RegisterInterfaces is called in the correct order within your AppModuleBasic. Every module must register its own interfaces and service descriptions correctly [8][6]. - Check for Conflicts: Search your codebase for multiple calls to RegisterImplementations or RegisterMsgServiceDesc involving the same types or services. - Review Custom Types: If you are using custom TypeURLs, ensure they are unique and not colliding with standard SDK modules [2]. - Use Proper Registration Pattern: Always call msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) inside your module's RegisterInterfaces method [8]. For complex debugging, ensure that you are not accidentally including multiple versions of the same module or conflicting proto definitions, as these can lead to duplicate type registration at runtime [7].
Citations:
- 1: https://github.com/cosmos/cosmos-sdk/blob/release/v0.54.x/codec/types/interface_registry.go
- 2: Registering protobuf service or interface type twice should cause an error cosmos/cosmos-sdk#7679
- 3: https://github.com/cosmos/cosmos-sdk/blob/dfd47f5b449f558a855da284a9a7eabbfbad435d/baseapp/msg_service_router.go
- 4: https://pkg.go.dev/github.com/cosmos/cosmos-sdk/codec/types
- 5: https://github.com/cosmos/cosmos-sdk/blob/master/types/msgservice/msg_service.go
- 6: [Bug] [Documentation]: I'm adding a KYC feature into auth module cosmos/cosmos-sdk#17422
- 7: fix: make RegisterMsgServiceDesc not use the global registry cosmos/cosmos-sdk#19835
- 8: https://docs.cosmos.network/sdk/latest/tutorials/example/03-build-a-module
🌐 Web query:
Cosmos SDK msgservice.RegisterMsgServiceDesc duplicate same service idempotent panic source code
💡 Result:
In the Cosmos SDK, attempting to register the same gRPC Msg service multiple times will trigger a panic [1][2]. This design is intentional: the framework treats duplicate service registration as a critical configuration error or a potential security conflict, as it implies that two separate modules are attempting to handle the same service [3]. The panic primarily originates from the MsgServiceRouter during its RegisterService method, which is responsible for routing gRPC requests [1][2]. If RegisterService is called for a service that has already been registered, or if it is invoked before the necessary message types have been registered via the InterfaceRegistry (using msgservice.RegisterMsgServiceDesc), the application will panic [1][2][4]. Key technical points regarding this behavior: 1. Intentional Enforcement: The Cosmos SDK intentionally avoids idempotency for service registration. By panicking on duplicates, the framework ensures that developers catch configuration errors during initialization rather than encountering unpredictable routing behavior at runtime [3]. 2. Order of Operations: A common cause for related panics (e.g., "type_url... has not been registered yet") is failing to call msgservice.RegisterMsgServiceDesc inside the RegisterInterfaces method of a module's AppModuleBasic before RegisterService is called by the application [4]. 3. Source Code Location: The enforcement logic resides in baseapp/msg_service_router.go. The RegisterService function iterates through service methods and triggers a panic if registration fails, which includes checks for existing service mappings [1][2]. 4. Evolution: While there have been community discussions and refactors (such as PR #19835) to improve how RegisterMsgServiceDesc handles type registration and global registry usage, the core principle of preventing duplicate service registration through panic remains a standard practice in the SDK to maintain system integrity [5].
Citations:
- 1: https://github.com/cosmos/cosmos-sdk/blob/main/baseapp/msg_service_router.go
- 2: https://github.com/cosmos/cosmos-sdk/blob/v0.53.6/baseapp/msg_service_router.go
- 3: Registering protobuf service or interface type twice should cause an error cosmos/cosmos-sdk#7679
- 4: [Bug] [Documentation]: I'm adding a KYC feature into auth module cosmos/cosmos-sdk#17422
- 5: fix: make RegisterMsgServiceDesc not use the global registry cosmos/cosmos-sdk#19835
Remove the duplicate EVM/ERC20 registrations at client/chain/context.go:186-187. createInjectiveProtoCodec() already registers both modules earlier, and each RegisterInterfaces call also registers the module’s Msg service descriptor on the same registry. Calling them again here can panic during codec initialization.
Suggested fix
- evmtypes.RegisterInterfaces(interfaceRegistry)
- erc20types.RegisterInterfaces(interfaceRegistry)
probabilistic.RegisterInterfaces(interfaceRegistry)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| evmtypes.RegisterInterfaces(interfaceRegistry) | |
| erc20types.RegisterInterfaces(interfaceRegistry) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@client/chain/context.go` around lines 186 - 187, The duplicate EVM/ERC20
interface registrations in createInjectiveProtoCodec() should be removed from
the extra registration block in context.go, since those modules are already
registered earlier in the same codec setup. Keep the unique registrations only
once by relying on the existing createInjectiveProtoCodec() setup and delete the
repeated evmtypes.RegisterInterfaces and erc20types.RegisterInterfaces calls to
avoid re-registering the Msg service descriptors and triggering codec
initialization panics.
| // new_token_oracle_type is used to obtain the USD price of the ERC20 token | ||
| // (in relation with token_price_id) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Update the comment to reference new_token_price_id.
Line 439 still points to token_price_id, but MsgUpdateRateLimit only defines new_token_price_id. This will generate misleading API docs for the new field.
Suggested fix
- // (in relation with token_price_id)
+ // (in relation with new_token_price_id)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // new_token_oracle_type is used to obtain the USD price of the ERC20 token | |
| // (in relation with token_price_id) | |
| // new_token_oracle_type is used to obtain the USD price of the ERC20 token | |
| // (in relation with new_token_price_id) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@proto/injective/peggy/v1/msgs.proto` around lines 438 - 439, The comment for
MsgUpdateRateLimit still references token_price_id, which is misleading because
the message only defines new_token_price_id. Update the surrounding comment near
the new_token_oracle_type/new_token_price_id fields in the protobuf definition
so the docs and generated API text point to new_token_price_id consistently.
Summary by CodeRabbit
New Features
Bug Fixes
Chores