Skip to content

Commit

Permalink
Ccip-5255 view updates to include chain selector and id , additional …
Browse files Browse the repository at this point in the history
…validation in offchain config (#16467)

* view updates and ocr3 validation

* handle only for EVM

* lint

* review comments

* fixes

* fix solana

* another fix
  • Loading branch information
AnieeG authored Feb 19, 2025
1 parent 080caf9 commit 4f3983a
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 21 deletions.
53 changes: 36 additions & 17 deletions deployment/ccip/changeset/cs_ccip_home.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,35 @@ func findTokenInfo(tokens []tokenInfo, address common.Address) (string, uint8, e
return "", 0, fmt.Errorf("token %s not found in available tokens", address)
}

func validateExecOffchainConfig(e deployment.Environment, c *pluginconfig.ExecuteOffchainConfig, selector uint64, state CCIPOnChainState) error {
if err := c.Validate(); err != nil {
return fmt.Errorf("invalid execute off-chain config: %w", err)
}
// get offRamp
if err := state.ValidateRamp(selector, OffRamp); err != nil {
return fmt.Errorf("validate offRamp: %w", err)
}
permissionLessExecutionThresholdSeconds, err := state.OffRampPermissionLessExecutionThresholdSeconds(e.GetContext(), e, selector)
if err != nil {
return fmt.Errorf("fetch permissionLessExecutionThresholdSeconds: %w", err)
}
if uint32(c.MessageVisibilityInterval.Duration().Seconds()) != permissionLessExecutionThresholdSeconds {
return fmt.Errorf("MessageVisibilityInterval=%s does not match the permissionlessExecutionThresholdSeconds in dynamic config =%d for chain %d",
c.MessageVisibilityInterval.Duration(), permissionLessExecutionThresholdSeconds, selector)
}
for _, observerConfig := range c.TokenDataObservers {
switch observerConfig.Type {
case pluginconfig.USDCCCTPHandlerType:
if err := validateUSDCConfig(observerConfig.USDCCCTPObserverConfig, state); err != nil {
return fmt.Errorf("invalid USDC config: %w", err)
}
default:
return fmt.Errorf("unknown token observer config type: %s", observerConfig.Type)
}
}
return nil
}

func validateCommitOffchainConfig(c *pluginconfig.CommitOffchainConfig, selector uint64, feedChainSel uint64, state CCIPOnChainState) error {
if err := c.Validate(); err != nil {
return fmt.Errorf("invalid commit off-chain config: %w", err)
Expand Down Expand Up @@ -147,7 +176,7 @@ type CCIPOCRParams struct {
ExecuteOffChainConfig *pluginconfig.ExecuteOffchainConfig
}

func (c CCIPOCRParams) Validate(selector uint64, feedChainSel uint64, state CCIPOnChainState) error {
func (c CCIPOCRParams) Validate(e deployment.Environment, selector uint64, feedChainSel uint64, state CCIPOnChainState) error {
if err := c.OCRParameters.Validate(); err != nil {
return fmt.Errorf("invalid OCR parameters: %w", err)
}
Expand All @@ -160,19 +189,9 @@ func (c CCIPOCRParams) Validate(selector uint64, feedChainSel uint64, state CCIP
}
}
if c.ExecuteOffChainConfig != nil {
if err := c.ExecuteOffChainConfig.Validate(); err != nil {
if err := validateExecOffchainConfig(e, c.ExecuteOffChainConfig, selector, state); err != nil {
return fmt.Errorf("invalid execute off-chain config: %w", err)
}
for _, observerConfig := range c.ExecuteOffChainConfig.TokenDataObservers {
switch observerConfig.Type {
case pluginconfig.USDCCCTPHandlerType:
if err := validateUSDCConfig(observerConfig.USDCCCTPObserverConfig, state); err != nil {
return fmt.Errorf("invalid USDC config: %w", err)
}
default:
return fmt.Errorf("unknown token observer config type: %s", observerConfig.Type)
}
}
}
return nil
}
Expand Down Expand Up @@ -227,7 +246,7 @@ func WithDefaultExecuteOffChainConfig(tokenDataObservers []pluginconfig.TokenDat
RelativeBoostPerWaitHour: globals.RelativeBoostPerWaitHour,
InflightCacheExpiry: *config.MustNewDuration(globals.InflightCacheExpiry),
RootSnoozeTime: *config.MustNewDuration(globals.RootSnoozeTime),
MessageVisibilityInterval: *config.MustNewDuration(globals.FirstBlockAge),
MessageVisibilityInterval: *config.MustNewDuration(globals.PermissionLessExecutionThreshold),
BatchingStrategyID: globals.BatchingStrategyID,
TokenDataObservers: tokenDataObservers,
}
Expand Down Expand Up @@ -449,7 +468,7 @@ func (p SetCandidatePluginInfo) String() string {
return fmt.Sprintf("PluginType: %s, Chains: %v", p.PluginType.String(), allchains)
}

func (p SetCandidatePluginInfo) Validate(state CCIPOnChainState, homeChain uint64, feedChain uint64) error {
func (p SetCandidatePluginInfo) Validate(e deployment.Environment, state CCIPOnChainState, homeChain uint64, feedChain uint64) error {
if p.PluginType != types.PluginTypeCCIPCommit &&
p.PluginType != types.PluginTypeCCIPExec {
return errors.New("PluginType must be set to either CCIPCommit or CCIPExec")
Expand Down Expand Up @@ -489,7 +508,7 @@ func (p SetCandidatePluginInfo) Validate(state CCIPOnChainState, homeChain uint6
if err := decodedChainConfig.Validate(); err != nil {
return fmt.Errorf("invalid chain config: %w", err)
}
err = params.Validate(chainSelector, feedChain, state)
err = params.Validate(e, chainSelector, feedChain, state)
if err != nil {
return fmt.Errorf("invalid ccip ocr params: %w", err)
}
Expand Down Expand Up @@ -558,7 +577,7 @@ func (a AddDonAndSetCandidateChangesetConfig) Validate(e deployment.Environment,
return err
}

if err := a.PluginInfo.Validate(state, a.HomeChainSelector, a.FeedChainSelector); err != nil {
if err := a.PluginInfo.Validate(e, state, a.HomeChainSelector, a.FeedChainSelector); err != nil {
return fmt.Errorf("validate plugin info %s: %w", a.PluginInfo.String(), err)
}
for chainSelector := range a.PluginInfo.OCRConfigPerRemoteChainSelector {
Expand Down Expand Up @@ -757,7 +776,7 @@ func (s SetCandidateChangesetConfig) Validate(e deployment.Environment, state CC

chainToDonIDs := make(map[uint64]uint32)
for _, plugin := range s.PluginInfo {
if err := plugin.Validate(state, s.HomeChainSelector, s.FeedChainSelector); err != nil {
if err := plugin.Validate(e, state, s.HomeChainSelector, s.FeedChainSelector); err != nil {
return nil, fmt.Errorf("validate plugin info %s: %w", plugin.String(), err)
}
for chainSelector := range plugin.OCRConfigPerRemoteChainSelector {
Expand Down
58 changes: 57 additions & 1 deletion deployment/ccip/changeset/cs_ccip_home_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"github.com/stretchr/testify/assert"
"golang.org/x/exp/maps"

"github.com/smartcontractkit/chainlink-common/pkg/config"

"github.com/smartcontractkit/chainlink-ccip/chainconfig"
cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
"github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext"
Expand Down Expand Up @@ -247,6 +249,7 @@ func Test_SetCandidate(t *testing.T) {
MinDelay: 0,
}
}

tokenConfig := changeset.NewTestTokenConfig(state.Chains[tenv.FeedChainSel].USDFeeds)
_, err = commonchangeset.Apply(t, tenv.Env,
map[uint64]*proposalutils.TimelockExecutionContracts{
Expand Down Expand Up @@ -280,6 +283,16 @@ func Test_SetCandidate(t *testing.T) {
OCRConfigPerRemoteChainSelector: map[uint64]changeset.CCIPOCRParams{
dest: changeset.DeriveCCIPOCRParams(
changeset.WithDefaultExecuteOffChainConfig(nil),
// change the default config to make MessageVisibilityInterval != PermissionLessExecutionThresholdSeconds
changeset.WithOCRParamOverride(func(params *changeset.CCIPOCRParams) {
dCfg, err := state.Chains[dest].OffRamp.GetDynamicConfig(&bind.CallOpts{
Context: ctx,
})
require.NoError(t, err)
params.ExecuteOffChainConfig.MessageVisibilityInterval =
*config.MustNewDuration(
time.Duration(dCfg.PermissionLessExecutionThresholdSeconds + uint32(time.Second)))
}),
),
},
PluginType: types.PluginTypeCCIPExec,
Expand All @@ -288,8 +301,51 @@ func Test_SetCandidate(t *testing.T) {
},
),
)
require.NoError(t, err)
require.Error(t, err)
require.Contains(t, err.Error(), "does not match the permissionlessExecutionThresholdSeconds in dynamic config")

// now set the correct config
_, err = commonchangeset.Apply(t, tenv.Env,
map[uint64]*proposalutils.TimelockExecutionContracts{
tenv.HomeChainSel: {
Timelock: state.Chains[tenv.HomeChainSel].Timelock,
CallProxy: state.Chains[tenv.HomeChainSel].CallProxy,
},
},
commonchangeset.Configure(
deployment.CreateLegacyChangeSet(changeset.SetCandidateChangeset),
changeset.SetCandidateChangesetConfig{
SetCandidateConfigBase: changeset.SetCandidateConfigBase{
HomeChainSelector: tenv.HomeChainSel,
FeedChainSelector: tenv.FeedChainSel,
MCMS: mcmsConfig,
},
PluginInfo: []changeset.SetCandidatePluginInfo{
{
OCRConfigPerRemoteChainSelector: map[uint64]changeset.CCIPOCRParams{
dest: changeset.DeriveCCIPOCRParams(
changeset.WithDefaultCommitOffChainConfig(
tenv.FeedChainSel,
tokenConfig.GetTokenInfo(logger.TestLogger(t),
state.Chains[dest].LinkToken.Address(),
state.Chains[dest].Weth9.Address())),
),
},
PluginType: types.PluginTypeCCIPCommit,
},
{
OCRConfigPerRemoteChainSelector: map[uint64]changeset.CCIPOCRParams{
dest: changeset.DeriveCCIPOCRParams(
changeset.WithDefaultExecuteOffChainConfig(nil),
),
},
PluginType: types.PluginTypeCCIPExec,
},
},
},
),
)
require.NoError(t, err)
// after setting a new candidate on both plugins, the candidate config digest
// should be nonzero.
candidateDigestCommitAfter, err := ccipHome.GetCandidateDigest(&bind.CallOpts{
Expand Down
3 changes: 2 additions & 1 deletion deployment/ccip/changeset/cs_deploy_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
chainsel "github.com/smartcontractkit/chain-selectors"

"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/globals"
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/v1_2_0/router"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/v1_6_0/ccip_home"
Expand Down Expand Up @@ -150,7 +151,7 @@ func (c OffRampParams) Validate(ignoreGasForCallExactCheck bool) error {
func DefaultOffRampParams() OffRampParams {
return OffRampParams{
GasForCallExactCheck: uint16(5000),
PermissionLessExecutionThresholdSeconds: uint32(24 * 60 * 60),
PermissionLessExecutionThresholdSeconds: uint32(globals.PermissionLessExecutionThreshold.Seconds()),
}
}

Expand Down
2 changes: 1 addition & 1 deletion deployment/ccip/changeset/globals/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const (
ConfigTypeActive ConfigType = "active"
ConfigTypeCandidate ConfigType = "candidate"
// ========= Changeset Defaults =========
FirstBlockAge = 8 * time.Hour
PermissionLessExecutionThreshold = 8 * time.Hour
RemoteGasPriceBatchWriteFrequency = 30 * time.Minute
TokenPriceBatchWriteFrequency = 30 * time.Minute
BatchGasLimit = 6_500_000
Expand Down
54 changes: 54 additions & 0 deletions deployment/ccip/changeset/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (

"github.com/Masterminds/semver/v3"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers"
solOffRamp "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_offramp"
solState "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/state"

"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/v1_5_1/burn_from_mint_token_pool"

Expand Down Expand Up @@ -391,6 +393,52 @@ type CCIPOnChainState struct {
AptosChains map[uint64]AptosCCIPChainState
}

func (s CCIPOnChainState) OffRampPermissionLessExecutionThresholdSeconds(ctx context.Context, env deployment.Environment, selector uint64) (uint32, error) {
family, err := chain_selectors.GetSelectorFamily(selector)
if err != nil {
return 0, err
}
switch family {
case chain_selectors.FamilyEVM:
c, ok := s.Chains[selector]
if !ok {
return 0, fmt.Errorf("chain %d not found in the state", selector)
}
offRamp := c.OffRamp
if offRamp == nil {
return 0, fmt.Errorf("offramp not found in the state for chain %d", selector)
}
dCfg, err := offRamp.GetDynamicConfig(&bind.CallOpts{
Context: ctx,
})
if err != nil {
return dCfg.PermissionLessExecutionThresholdSeconds, fmt.Errorf("fetch dynamic config from offRamp %s for chain %d: %w", offRamp.Address().String(), selector, err)
}
return dCfg.PermissionLessExecutionThresholdSeconds, nil
case chain_selectors.FamilySolana:
c, ok := s.SolChains[selector]
if !ok {
return 0, fmt.Errorf("chain %d not found in the state", selector)
}
chain, ok := env.SolChains[selector]
if !ok {
return 0, fmt.Errorf("solana chain %d not found in the environment", selector)
}
if c.OffRamp.IsZero() {
return 0, fmt.Errorf("offramp not found in existing state, deploy the offramp first for chain %d", selector)
}
var offRampConfig solOffRamp.Config
offRampConfigPDA, _, _ := solState.FindOfframpConfigPDA(c.OffRamp)
err := chain.GetAccountDataBorshInto(context.Background(), offRampConfigPDA, &offRampConfig)
if err != nil {
return 0, fmt.Errorf("offramp config not found in existing state, initialize the offramp first %d", chain.Selector)
}
// #nosec G115
return uint32(offRampConfig.EnableManualExecutionAfter), nil
}
return 0, fmt.Errorf("unsupported chain family %s", family)
}

func (s CCIPOnChainState) Validate() error {
for sel, chain := range s.Chains {
// cannot have static link and link together
Expand Down Expand Up @@ -461,6 +509,12 @@ func (s CCIPOnChainState) View(chains []uint64) (map[string]view.ChainView, erro
if chainInfo.ChainName == "" {
name = strconv.FormatUint(chainSelector, 10)
}
chainView.ChainSelector = chainSelector
id, err := chain_selectors.GetChainIDFromSelector(chainSelector)
if err != nil {
return nil, fmt.Errorf("failed to get chain id from selector %d: %w", chainSelector, err)
}
chainView.ChainID = id
m[name] = chainView
}
return m, nil
Expand Down
2 changes: 2 additions & 0 deletions deployment/ccip/view/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
)

type ChainView struct {
ChainSelector uint64 `json:"chainSelector,omitempty"`
ChainID string `json:"chainID,omitempty"`
// v1.0
RMNProxy map[string]v1_0.RMNProxyView `json:"rmnProxy,omitempty"`
// v1.2
Expand Down
4 changes: 3 additions & 1 deletion deployment/solana_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (

solCommonUtil "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/common"
"github.com/smartcontractkit/chainlink-common/pkg/logger"

"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/globals"
)

var (
Expand All @@ -28,7 +30,7 @@ var (
SolDefaultMaxFeeJuelsPerMsg = solBinary.Uint128{Lo: 300000000, Hi: 0, Endianness: nil}
SPL2022Tokens = "SPL2022Tokens"
SPLTokens = "SPLTokens"
EnableExecutionAfter = int64(1800) // 30min
EnableExecutionAfter = int64(globals.PermissionLessExecutionThreshold.Seconds())
)

// SolChain represents a Solana chain.
Expand Down

0 comments on commit 4f3983a

Please sign in to comment.