feat: add support for dual rate limits#2053
Draft
chris-de-leon-cll wants to merge 5 commits into
Draft
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates the token deployment/configuration tooling to support two explicit TPRL rate-limit buckets (default-finality and fast-finality) without inferring bucket selection from on-chain AllowedFinalityConfig. It introduces canonical per-bucket configuration structures + normalization, propagates bucket data through the transfer-configuration flow, and updates the EVM v2.0.0 configure logic + adapter to diff and batch-set per-bucket rate limits.
Changes:
- Introduces explicit rate-limit bucket modeling (
RateLimitConfig,Outbounds,Normalize(), per-bucket validation) and extendsRemoteChainConfigto carry per-direction bucket slices. - Updates ConfigureTokensForTransfers to pass/normalize full counterpart config and reconcile inbound/outbound buckets independently.
- Updates EVM v2.0.0 configure sequence + adapter to build/apply a list of desired buckets (default always, FF only when explicitly paired) and batch
SetRateLimitConfigper bucket.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| deployment/tokens/rate_limits.go | Adds bucket model + normalization, TPRL verification changes, and lane bucket building logic. |
| deployment/tokens/product.go | Adds float-input validation and extends RemoteChainConfig with inbound/outbound bucket slices + helpers. |
| deployment/tokens/configure_tokens_for_transfers.go | Propagates normalized bucket slices using full counterpart RemoteChainConfig. |
| deployment/tokens/configure_tokens_for_transfers_test.go | Adds unit tests for normalization + bucket access helpers. |
| chains/evm/deployment/v2_0_0/sequences/tokens/configure_token_pool_for_remote_chain.go | Switches from finality-based inference to explicit per-bucket desired updates with batched writes. |
| chains/evm/deployment/v2_0_0/sequences/tokens/configure_token_pool_for_remote_chain_test.go | Updates tests to assert scalar fields only affect the default bucket unless FF slices are explicitly provided. |
| chains/evm/deployment/v2_0_0/sequences/tokens/configure_token_for_transfers_test.go | Updates expectations/documentation around which bucket scalar floats populate. |
| chains/evm/deployment/v2_0_0/adapters/tokens.go | Updates adapter to map RateLimitBuckets directly to SetRateLimitConfig args (no finality inference). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+124
to
151
| if len(input.Outbounds) == 0 { | ||
| if err := input.RateLimit.Validate(); err != nil { | ||
| return fmt.Errorf("outbound rate limiter config for remote chain %d is invalid: %w", remoteSelector, err) | ||
| } | ||
| if input.RateLimit.Rate > input.RateLimit.Capacity { | ||
| return fmt.Errorf("outbound rate limiter config for remote chain %d has rate greater than capacity", remoteSelector) | ||
| } | ||
|
|
||
| if len(input.Outbounds) > 2 { | ||
| return fmt.Errorf("at most two outbound rate limit buckets allowed for remote chain %d", remoteSelector) | ||
| } | ||
|
|
||
| defaultCount, fastFinCount := 0, 0 | ||
| for _, rl := range input.Outbounds { | ||
| if err := rl.RateLimitConfig.Validate(); err != nil { | ||
| return fmt.Errorf("outbound rate limiter config for remote chain %d is invalid: %w", remoteSelector, err) | ||
| } | ||
| if rl.FastFinality { | ||
| fastFinCount++ | ||
| } else { | ||
| defaultCount++ | ||
| } | ||
| } | ||
|
|
||
| if defaultCount > 1 { | ||
| return fmt.Errorf("multiple outbound rate limit buckets with fastFinality=false provided for remote chain %d", remoteSelector) | ||
| } | ||
| if fastFinCount > 1 { | ||
| return fmt.Errorf("multiple outbound rate limit buckets with fastFinality=true provided for remote chain %d", remoteSelector) | ||
| } |
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TPRL: explicit default + fast-finality buckets (no pool-finality inference)
Summary
This change replaces the previous idea that v2 token pool tooling could infer which TPRL bucket to update by reading the pool’s
AllowedFinalityConfig. That model was incorrect: allowed finality can combine modes such that both default-finality and fast-finality paths remain relevant over time, so both buckets may matter operationally.Instead, the deployment stack now treats bucket selection as explicit configuration: legacy scalar
InboundRateLimiterConfig/OutboundRateLimiterConfigreconcile to the default (FastFinality=false) bucket via normalization, and an optional fast-finality bucket is applied only when the normalized config includes paired per-direction slices (OutboundRateLimits/InboundRateLimits, or equivalent TPRLRemoteOutbounds.Outboundsrows withFastFinality=true). The EVM v2 adapter batches oneSetRateLimitConfigcall with one arg per bucket that actually needs an update, using each bucket’sFastFinalityflag directly—without deriving it from on-chain finality.Motivation
Earlier framing suggested users only ever cared about “the active” bucket and that omitting the inactive bucket was safely ignorable. In practice:
AllowedFinalityConfigconflates what the pool allows with which limits should be written, and understates cases where both buckets should be maintained.This PR aligns tooling with an explicit multi-bucket contract while preserving backwards-compatible scalar fields as aliases for the default bucket after
Normalize().What changed
Core model (
deployment/tokens)RateLimitConfig(RateLimitConfig+FastFinality) and expandsRemoteOutboundswith canonicalOutbounds []RateLimitConfig, keepingRateLimitas the default-bucket alias.Normalize(),BucketForFinality(), validation onRateLimiterConfigFloatInput, and stricter TPRL input verification (at most two outbound buckets per remote, at most one perFastFinalityvalue).RemoteChainConfigwithInboundRateLimits/OutboundRateLimitsplusNormalize(),GetOutbounds(), andGetInbounds()so inbound/outbound directions reconcile independently the same way asRemoteOutbounds.buildTPRLRemotesForSetRateLimitsLane, producingTPRLRemotes.RateLimitBucketsfor the adapter; counterpart configs are normalized when stitching lanes.Configure tokens for transfers
convertRemoteChainConfignow receives the full counterpartRemoteChainConfig(not only its outbound scalar), copies normalized scalars and bucket slices for both directions, and returnsoutCfg.Normalize().EVM v2 configure sequence
ConfigureTokenPoolForRemoteChainnormalizesRemoteChainConfig, buildsrlInputs: always the default bucket from legacy scalars, optionally appends fast-finality when both outbound and inbound FF buckets exist after normalization, and passes that slice tomaybeUpdateRateLimiters, which diffs per bucket and emits batchedSetRateLimitConfigargs only where needed.EVM v2 adapter
SetTokenPoolRateLimitsapplies optionalAllowedFinalityConfigwhen requested (read-before-write only in that case), then mapsinput.RateLimitBucketsdirectly toRateLimitConfigArgs—nofinality.RawWaitForFinalitycomparison for pickingFastFinality.Reviewer / operator notes
AllowedFinalityConfigremains meaningful for what the pool accepts, but it is not used to decide where scalar TPRL floats land; scalars stay on the default bucket unless you also supply explicit paired FF slice configuration.FastFinality=true).