-
Notifications
You must be signed in to change notification settings - Fork 2k
Add set rate limits #22503
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Add set rate limits #22503
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,6 +70,9 @@ var _ cldf.ChangeSet[ExtendTokenPoolLookupTableConfig] = ExtendTokenPoolLookupTa | |
| // transfer mint authority back to pool signer PDA | ||
| var _ cldf.ChangeSet[TransferMintAuthorityToSignerPDAConfig] = TransferMintAuthorityToSignerPDA | ||
|
|
||
| // set chain rate limits on a token pool | ||
| var _ cldf.ChangeSet[SetChainRateLimitConfig] = SetChainRateLimit | ||
|
|
||
| // append mcms txns generated from solanainstructions | ||
| func appendTxs(instructions []solana.Instruction, tokenPool solana.PublicKey, poolType cldf.ContractType, txns *[]mcmsTypes.Transaction) error { | ||
| for _, ixn := range instructions { | ||
|
|
@@ -3258,3 +3261,198 @@ func SetRateLimitAdmin(e cldf.Environment, cfg SetRateLimitAdminConfig) (cldf.Ch | |
|
|
||
| return cldf.ChangesetOutput{}, nil | ||
| } | ||
|
|
||
| type ChainRateLimitEntry struct { | ||
| SolTokenPubKey string | ||
| PoolType cldf.ContractType | ||
| Metadata string | ||
| RemoteChainSelector uint64 | ||
| RateLimiterConfig RateLimiterConfig | ||
| } | ||
|
|
||
| type SetChainRateLimitConfig struct { | ||
| SolChainSelector uint64 | ||
| ChainRateLimitConfigs []ChainRateLimitEntry | ||
| MCMS *proposalutils.TimelockConfig | ||
| } | ||
|
|
||
| func (cfg SetChainRateLimitConfig) Validate(e cldf.Environment, chainState solanastateview.CCIPChainState) error { | ||
| chain := e.BlockChains.SolanaChains()[cfg.SolChainSelector] | ||
| for _, entry := range cfg.ChainRateLimitConfigs { | ||
| tokenPubKey := solana.MustPublicKeyFromBase58(entry.SolTokenPubKey) | ||
| if entry.PoolType == "" { | ||
| return errors.New("pool type must be defined") | ||
| } | ||
| if entry.RemoteChainSelector == 0 { | ||
| return errors.New("remote chain selector must be defined") | ||
| } | ||
|
Comment on lines
+3283
to
+3288
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we checking somewhere that they are also valid?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the remote chain selector you mean? Yes, in line 3296 |
||
| if err := chainState.CommonValidation(e, cfg.SolChainSelector, tokenPubKey); err != nil { | ||
| return err | ||
| } | ||
| if err := chainState.ValidatePoolDeployment(&e, entry.PoolType, cfg.SolChainSelector, tokenPubKey, true, entry.Metadata); err != nil { | ||
| return err | ||
| } | ||
| tokenPool := chainState.GetActiveTokenPool(entry.PoolType, entry.Metadata) | ||
| isSup, _, err := isSupportedChain(chain, tokenPubKey, tokenPool, entry.PoolType, entry.RemoteChainSelector) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to check if remote chain %d is supported: %w", entry.RemoteChainSelector, err) | ||
| } | ||
| if !isSup { | ||
| return fmt.Errorf("remote chain %d is not configured for token %s on pool %s; use SetupTokenPoolForRemoteChain first", entry.RemoteChainSelector, entry.SolTokenPubKey, tokenPool.String()) | ||
| } | ||
| if err := entry.RateLimiterConfig.Validate(); err != nil { | ||
| return fmt.Errorf("invalid rate limiter config for token %s remote chain %d: %w", entry.SolTokenPubKey, entry.RemoteChainSelector, err) | ||
| } | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| func SetChainRateLimit(e cldf.Environment, cfg SetChainRateLimitConfig) (cldf.ChangesetOutput, error) { | ||
|
|
||
| state, err := stateview.LoadOnchainState(e) | ||
| if err != nil { | ||
| return cldf.ChangesetOutput{}, err | ||
| } | ||
| chainState := state.SolChains[cfg.SolChainSelector] | ||
| if err := cfg.Validate(e, chainState); err != nil { | ||
| return cldf.ChangesetOutput{}, err | ||
| } | ||
|
|
||
| var mcmsTxs []mcmsTypes.Transaction | ||
|
|
||
| for _, entry := range cfg.ChainRateLimitConfigs { | ||
| chain := e.BlockChains.SolanaChains()[cfg.SolChainSelector] | ||
| tokenPubKey := solana.MustPublicKeyFromBase58(entry.SolTokenPubKey) | ||
| tokenPool := chainState.GetActiveTokenPool(entry.PoolType, entry.Metadata) | ||
| poolConfigPDA, remoteChainConfigPDA := getPoolPDAs(tokenPubKey, tokenPool, entry.RemoteChainSelector) | ||
|
|
||
| tokenPoolUsingMcms := solanastateview.IsSolanaProgramOwnedByTimelock( | ||
| &e, | ||
| chain, | ||
| chainState, | ||
| entry.PoolType, | ||
| tokenPubKey, | ||
| entry.Metadata, | ||
| ) | ||
| authority := GetAuthorityForIxn( | ||
| &e, | ||
| chain, | ||
| chainState, | ||
| entry.PoolType, | ||
| tokenPubKey, | ||
| entry.Metadata, | ||
| ) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aren't both checks doing pretty much the same thing? At this point AFAIK there should be only 2 options, either deployer and not MCMS, or using MCMS and the authority is timelock, right? |
||
|
|
||
| var ixns []solana.Instruction | ||
|
|
||
| // There is a bug on the token pool contract which does not allow us to set the actual rate limits directly. | ||
| // We have to setup dummy limits first and then update it. | ||
| // This workaround is only needed when enabling a rate limit that is currently disabled on-chain, | ||
| // not when updating already-enabled limits, to avoid resetting that direction's token bucket. | ||
| _, onChainConfig, err := isSupportedChain(chain, tokenPubKey, tokenPool, entry.PoolType, entry.RemoteChainSelector) | ||
| // If the account doesn't exist yet (e.g. during token expansion where MCMS batches | ||
| // init_chain_remote_config and set_chain_rate_limit in a single proposal), we treat | ||
| // both directions as uninitialized and always prepend the dummy instruction. | ||
|
|
||
| needsDummy := false | ||
| if err != nil { | ||
| needsDummy = entry.RateLimiterConfig.Inbound.Enabled || entry.RateLimiterConfig.Outbound.Enabled | ||
| } else { | ||
| needsDummy = (entry.RateLimiterConfig.Inbound.Enabled && !onChainConfig.InboundRateLimit.Cfg.Enabled) || | ||
| (entry.RateLimiterConfig.Outbound.Enabled && !onChainConfig.OutboundRateLimit.Cfg.Enabled) | ||
| } | ||
| if needsDummy { | ||
| // Build dummy configs that preserve already-enabled directions. | ||
| // Only the direction(s) transitioning from disabled→enabled need the | ||
| // dummy disabled config; the other direction keeps its on-chain values | ||
| // so we don't reset its token bucket. | ||
| dummyInbound := onChainConfig.InboundRateLimit.Cfg | ||
| dummyOutbound := onChainConfig.OutboundRateLimit.Cfg | ||
|
agusaldasoro marked this conversation as resolved.
|
||
| if entry.RateLimiterConfig.Inbound.Enabled && !onChainConfig.InboundRateLimit.Cfg.Enabled { | ||
| dummyInbound = solBaseTokenPool.RateLimitConfig{Enabled: false, Capacity: 0, Rate: 0} | ||
| } | ||
| if entry.RateLimiterConfig.Outbound.Enabled && !onChainConfig.OutboundRateLimit.Cfg.Enabled { | ||
| dummyOutbound = solBaseTokenPool.RateLimitConfig{Enabled: false, Capacity: 0, Rate: 0} | ||
| } | ||
| var dummyIx solana.Instruction | ||
| switch entry.PoolType { | ||
| case shared.BurnMintTokenPool: | ||
| runSafely(func() { solBurnMintTokenPool.SetProgramID(tokenPool) }) | ||
| dummyIx, err = solBurnMintTokenPool.NewSetChainRateLimitInstruction( | ||
| entry.RemoteChainSelector, tokenPubKey, dummyInbound, dummyOutbound, | ||
| poolConfigPDA, remoteChainConfigPDA, authority, | ||
| ).ValidateAndBuild() | ||
| case shared.LockReleaseTokenPool: | ||
| runSafely(func() { solLockReleaseTokenPool.SetProgramID(tokenPool) }) | ||
| dummyIx, err = solLockReleaseTokenPool.NewSetChainRateLimitInstruction( | ||
| entry.RemoteChainSelector, tokenPubKey, dummyInbound, dummyOutbound, | ||
| poolConfigPDA, remoteChainConfigPDA, authority, | ||
| ).ValidateAndBuild() | ||
| case shared.CCTPTokenPool: | ||
| runSafely(func() { cctp_token_pool.SetProgramID(tokenPool) }) | ||
| dummyIx, err = cctp_token_pool.NewSetChainRateLimitInstruction( | ||
| entry.RemoteChainSelector, tokenPubKey, dummyInbound, dummyOutbound, | ||
| poolConfigPDA, remoteChainConfigPDA, authority, | ||
| ).ValidateAndBuild() | ||
| default: | ||
| return cldf.ChangesetOutput{}, fmt.Errorf("invalid pool type: %s", entry.PoolType) | ||
| } | ||
| if err != nil { | ||
| return cldf.ChangesetOutput{}, fmt.Errorf("failed to generate dummy rate limit instruction: %w", err) | ||
| } | ||
| ixns = append(ixns, dummyIx) | ||
| } | ||
|
|
||
| var ix solana.Instruction | ||
| switch entry.PoolType { | ||
| case shared.BurnMintTokenPool: | ||
| runSafely(func() { solBurnMintTokenPool.SetProgramID(tokenPool) }) | ||
| ix, err = solBurnMintTokenPool.NewSetChainRateLimitInstruction( | ||
| entry.RemoteChainSelector, tokenPubKey, | ||
| entry.RateLimiterConfig.Inbound, entry.RateLimiterConfig.Outbound, | ||
| poolConfigPDA, remoteChainConfigPDA, authority, | ||
| ).ValidateAndBuild() | ||
| case shared.LockReleaseTokenPool: | ||
| runSafely(func() { solLockReleaseTokenPool.SetProgramID(tokenPool) }) | ||
| ix, err = solLockReleaseTokenPool.NewSetChainRateLimitInstruction( | ||
| entry.RemoteChainSelector, tokenPubKey, | ||
| entry.RateLimiterConfig.Inbound, entry.RateLimiterConfig.Outbound, | ||
| poolConfigPDA, remoteChainConfigPDA, authority, | ||
| ).ValidateAndBuild() | ||
| case shared.CCTPTokenPool: | ||
| runSafely(func() { cctp_token_pool.SetProgramID(tokenPool) }) | ||
| ix, err = cctp_token_pool.NewSetChainRateLimitInstruction( | ||
| entry.RemoteChainSelector, tokenPubKey, | ||
| entry.RateLimiterConfig.Inbound, entry.RateLimiterConfig.Outbound, | ||
| poolConfigPDA, remoteChainConfigPDA, authority, | ||
| ).ValidateAndBuild() | ||
| default: | ||
| return cldf.ChangesetOutput{}, fmt.Errorf("invalid pool type: %s", entry.PoolType) | ||
| } | ||
| if err != nil { | ||
| return cldf.ChangesetOutput{}, fmt.Errorf("failed to generate set chain rate limit instruction: %w", err) | ||
| } | ||
| ixns = append(ixns, ix) | ||
|
|
||
| if tokenPoolUsingMcms { | ||
| if err := appendTxs(ixns, tokenPool, entry.PoolType, &mcmsTxs); err != nil { | ||
| return cldf.ChangesetOutput{}, fmt.Errorf("failed to generate mcms txn: %w", err) | ||
| } | ||
| } else { | ||
| if err := chain.Confirm(ixns); err != nil { | ||
| return cldf.ChangesetOutput{}, fmt.Errorf("failed to confirm instructions: %w", err) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if len(mcmsTxs) > 0 { | ||
| proposal, err := BuildProposalsForTxns( | ||
| e, cfg.SolChainSelector, "proposal to SetChainRateLimit in Solana", cfg.MCMS.MinDelay, mcmsTxs) | ||
|
|
||
| if err != nil { | ||
| return cldf.ChangesetOutput{}, fmt.Errorf("failed to build proposal: %w", err) | ||
| } | ||
| return cldf.ChangesetOutput{ | ||
| MCMSTimelockProposals: []mcms.TimelockProposal{*proposal}, | ||
| }, nil | ||
| } | ||
|
|
||
| return cldf.ChangesetOutput{}, nil | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be worth checking as well that our keys (deployer or timelock) are the RL admin? So that no one attempts to execute this on a pool that has been handed over to a customer, and gets their root committed to MCMS but cannot execute later the actual RL update.