feat: add support for default and fast finality rate limits#2053
feat: add support for default and fast finality rate limits#2053chris-de-leon-cll wants to merge 2 commits into
Conversation
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.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
deployment/tokens/rate_limits.go:134
- When
Outboundsis non-empty but has no default bucket,Normalizestill promotes the legacyRateLimitfield into a default bucket, but verification skips validatingRateLimitin that case. An invalid enabled alias (for example zero capacity/rate) can therefore pass verify and fail later or generate an invalid on-chain update.
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 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.RateLimit.Validate(); err != nil {
return fmt.Errorf("outbound rate limiter config for remote chain %d is invalid: %w", remoteSelector, err)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
deployment/tokens/rate_limits.go:305
- For a lane that contains only
fastFinality=truebuckets, this function leaves the legacy defaultOutboundRateLimiterConfig/InboundRateLimiterConfigas zero values while returning the fast-finality entry only inRateLimitBuckets. Pre-v2 EVM and Solana adapters ignoreRateLimitBucketsand still write those legacy fields, so a fast-finality-only config on a non-v2 pool will disable the default rate limits instead of being ignored as documented. Either reject fast-finality-only buckets for adapters that only consume the legacy fields, or preserve/no-op the legacy default config in that case.
defaultOutboundRL := RateLimiterConfig{}
defaultInboundRL := RateLimiterConfig{}
buckets := []TPRLRateLimitBucket{}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
chains/evm/deployment/v2_0_0/adapters/tokens.go:387
- When
RateLimitBucketsis empty (for example an emptyRemoteOutboundsentry or a finality-only TPRL run), this still schedules asetRateLimitConfigtransaction with an empty args array. The contract treats that as a no-op after permission checks, so the changeset can generate unnecessary proposal transactions. Skip this operation when there are no bucket args to apply.
report, err := cldf_ops.ExecuteOperation(b, token_pool.SetRateLimitConfig, evmChain, contract.FunctionInput[[]token_pool.RateLimitConfigArgs]{
ChainSelector: input.ChainSelector,
Address: tokenPoolAddr,
Args: args,
})
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated 6 comments.
Comments suppressed due to low confidence (1)
deployment/tokens/configure_tokens_for_transfers_test.go:812
- This assertion has the same pointer/value mismatch:
out.RateLimitis a*RateLimiterConfigFloatInput, so comparing it directly tocfgs[0].RateLimitwill fail despite correct normalization. Dereference the normalized alias after a non-nil check.
require.Equal(t, cfgs[0].RateLimit, out.RateLimit)
6df3e86 to
2a57011
Compare
| bucket, ok := input.GetBucketForFinality(false) | ||
| if !ok { | ||
| b.Logger.Warnf("skipping rate limiter config for token pool (%s) on chain %d since no default bucket was provided", datastore_utils.SprintRef(input.TokenPoolRef), input.ChainSelector) | ||
| return nil, nil | ||
| } |
There was a problem hiding this comment.
NOTE: this this check has been added to the pre-V2 EVM and Solana token adapters as a defense-in-depth measure to prevent accidental overwrites for the following scenario:
- Suppose the user only wants to edit the fast finality rate limits for a pair of pools, so they omit the default rate limits from the input and only include bidirectional FF rate limits
- During runtime, the changeset discovers that one of the pools that the user wants to update is v1.5.1 (which doesn't support FF rate limits) and the other is v2.0.0 (which does support FF rate limits)
- In this case, the original code checks the input for the default rate limits for the v1.5.1 pool and discovers that it only contains FF rate limits
- Consequently, the adapter can't tell whether the user wants to reset the default rate limits for the v1.5.1 pool or keep the existing onchain ones (the original code does NOT use a pointer type so this case is ambiguous)
- Without this check, the original code would reset the existing rate limits for the v1.5.1 pool and ignore the FF buckets
- This will cause the user to be confused about why things were unexpectedly reset for the v1.5.1 pool despite them only specifying FF rate limits
Test cases have been added to cover this situation.
| github.com/smartcontractkit/chainlink-ccip/chains/solana/deployment v0.0.0-00010101000000-000000000000 | ||
| github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260312233953-f588f8dc6d7c | ||
| github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260506222857-f76eec39d0c0 | ||
| github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260516222345-f2f143454dbd |
There was a problem hiding this comment.
|
Add Support for Explicit Default + Fast-Finality Rate Limits
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, this PR now allows you to specify both the default and fast finality (a.k.a. custom) rate limits. The current
InboundRateLimiterConfig/OutboundRateLimiterConfigis now an alias for the default (FastFinality=false) bucket for backwards compatibility purposes, and an optional fast-finality bucket is only applied if the pool supports it (it is a warning + no-op for other pre-v2 token adapters).Mixed pre-V2 pool and V2+ pool behavior:
YAML Examples
Setting Just the Default Rate Limit (works for all versions)
Setting Just the Custom Rate Limit (applies on-chain on v2 only)
Setting Both the Default and Custom Rate Limits (v2 applies both; legacy applies default only)