88 "github.com/ethereum/go-ethereum/common"
99 "github.com/smartcontractkit/chainlink-ccip/ccv/chains/evm/gobindings/generated/latest/fee_quoter"
1010 cldf_chain "github.com/smartcontractkit/chainlink-deployments-framework/chain"
11+ "golang.org/x/exp/maps"
1112
1213 "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/utils/operations/contract"
1314 onrampops "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_5_0/operations/onramp"
@@ -33,6 +34,7 @@ import (
3334
3435const (
3536 LinkFeeMultiplierPercent uint8 = 90
37+ NetworkFeeUSDCents = 10
3638)
3739
3840type FeeQuoterUpdate struct {
@@ -187,6 +189,8 @@ var (
187189 if datastore_utils .IsAddressRefEmpty (fq16Ref ) {
188190 return FeeQuoterUpdate {}, nil
189191 }
192+ output .ChainSelector = input .ChainSelector
193+ output .ExistingAddresses = input .ExistingAddresses
190194 // get feeQuoter 1.6 address meta
191195 metadataForFq16 , err := datastore_utils .FilterContractMetaByContractTypeAndVersion (
192196 input .ExistingAddresses ,
@@ -205,7 +209,12 @@ var (
205209 if len (metadataForFq16 ) > 1 {
206210 return FeeQuoterUpdate {}, fmt .Errorf ("multiple metadata entries found for FeeQuoter v1.6.3 on chain selector %d" , input .ChainSelector )
207211 }
208- fqOutput := metadataForFq16 [0 ].Metadata .(seq1_6.FeeQuoterImportConfigSequenceOutput )
212+ // Convert metadata to typed struct if needed
213+ fqOutput , err := datastore_utils.ConvertMetadataToType [seq1_6.FeeQuoterImportConfigSequenceOutput ](metadataForFq16 [0 ].Metadata )
214+ if err != nil {
215+ return FeeQuoterUpdate {}, fmt .Errorf ("failed to convert metadata to " +
216+ "FeeQuoterImportConfigSequenceOutput for chain selector %d: %w" , input .ChainSelector , err )
217+ }
209218 // is feeQuoter going to be deployed or fetched from existing addresses?
210219 feeQuoterRef := datastore_utils .GetAddressRef (
211220 input .ExistingAddresses ,
@@ -314,7 +323,8 @@ var (
314323 if len (onRampMetadata ) == 0 {
315324 return FeeQuoterUpdate {}, fmt .Errorf ("no metadata found for EVM2EVMOnRamp v1.5.0 on chain selector %d" , input .ChainSelector )
316325 }
317-
326+ output .ChainSelector = input .ChainSelector
327+ output .ExistingAddresses = input .ExistingAddresses
318328 // is feeQuoter going to be deployed or fetched from existing addresses?
319329 feeQuoter17Ref := datastore_utils .GetAddressRef (
320330 input .ExistingAddresses ,
@@ -329,22 +339,26 @@ var (
329339 var tokenTransferFeeConfigArgs []fee_quoter.FeeQuoterTokenTransferFeeConfigSingleTokenArgs
330340 var tokenTransferFeeConfigArgsForAll []fqops.TokenTransferFeeConfigArgs
331341 for _ , meta := range onRampMetadata {
332- onRampCfg := meta .Metadata .(seq1_5.OnRampImportConfigSequenceOutput )
342+ // Convert metadata to typed struct if needed
343+ onRampCfg , err := datastore_utils.ConvertMetadataToType [seq1_5.OnRampImportConfigSequenceOutput ](meta .Metadata )
344+ if err != nil {
345+ return FeeQuoterUpdate {}, fmt .Errorf ("failed to convert metadata to " +
346+ "OnRampImportConfigSequenceOutput for chain selector %d: %w" , input .ChainSelector , err )
347+ }
333348 if staticCfg .LinkToken == (common.Address {}) {
334349 staticCfg = fqops.StaticConfig {
335350 LinkToken : onRampCfg .StaticConfig .LinkToken ,
336351 MaxFeeJuelsPerMsg : onRampCfg .StaticConfig .MaxNopFeesJuels ,
337352 }
338353 }
339- var networkFeeUSDCents uint16
340- // NetworkFeeUSDCents is same across all feetokens in the same chain, so we can just take it from the first onRamp config
341- for _ , feeTokenCfg := range onRampCfg .FeeTokenConfig {
342- if feeTokenCfg .NetworkFeeUSDCents != 0 {
343- networkFeeUSDCents = uint16 (feeTokenCfg .NetworkFeeUSDCents )
344- break
345- }
354+
355+ chainFamilySelectorBytes := utils .GetSelectorHex (onRampCfg .RemoteChainSelector )
356+ // Safely convert ChainFamilySelector from []byte to [4]byte
357+ var chainFamilySelector [4 ]byte
358+ if len (chainFamilySelectorBytes ) < 4 {
359+ return FeeQuoterUpdate {}, fmt .Errorf ("ChainFamilySelector has invalid length %d (expected 4) for remote chain selector %d" , len (chainFamilySelectorBytes ), onRampCfg .RemoteChainSelector )
346360 }
347- chainFamilySelector := utils . GetSelectorHex ( onRampCfg . RemoteChainSelector )
361+ copy ( chainFamilySelector [:], chainFamilySelectorBytes [: 4 ] )
348362 destChainCfgs = append (destChainCfgs , fqops.DestChainConfigArgs {
349363 DestChainSelector : onRampCfg .RemoteChainSelector ,
350364 DestChainConfig : adapters.FeeQuoterDestChainConfig {
@@ -353,12 +367,12 @@ var (
353367 MaxPerMsgGasLimit : onRampCfg .DynamicConfig .MaxPerMsgGasLimit ,
354368 DestGasOverhead : onRampCfg .DynamicConfig .DestGasOverhead ,
355369 DestGasPerPayloadByteBase : uint8 (onRampCfg .DynamicConfig .DestGasPerPayloadByte ),
356- ChainFamilySelector : [ 4 ] byte ( chainFamilySelector ) ,
370+ ChainFamilySelector : chainFamilySelector ,
357371 DefaultTokenFeeUSDCents : onRampCfg .DynamicConfig .DefaultTokenFeeUSDCents ,
358372 DefaultTokenDestGasOverhead : onRampCfg .DynamicConfig .DefaultTokenDestGasOverhead ,
359373 DefaultTxGasLimit : uint32 (onRampCfg .StaticConfig .DefaultTxGasLimit ),
360- NetworkFeeUSDCents : networkFeeUSDCents ,
361- LinkFeeMultiplierPercent : 90 ,
374+ NetworkFeeUSDCents : NetworkFeeUSDCents ,
375+ LinkFeeMultiplierPercent : LinkFeeMultiplierPercent ,
362376 },
363377 })
364378 for token , tokenCfg := range onRampCfg .TokenTransferFeeConfig {
@@ -386,7 +400,7 @@ var (
386400 DestChainConfigArgs : destChainCfgs ,
387401 TokenTransferFeeConfigArgs : tokenTransferFeeConfigArgsForAll ,
388402 // TODO: what to do with price updaters for 1.5 if there is no 1.6 lanes here
389- PriceUpdaters : []common.Address {},
403+ // PriceUpdaters: []common.Address{},
390404 }
391405 } else {
392406 output .DestChainConfigs = destChainCfgs
@@ -395,3 +409,132 @@ var (
395409 return output , nil
396410 })
397411)
412+
413+ func MergeFeeQuoterUpdateOutputs (output16 , output15 FeeQuoterUpdate ) (FeeQuoterUpdate , error ) {
414+ result := output16
415+
416+ // ConstructorArgs: use output15 if output16 is empty
417+ if result .ConstructorArgs .IsEmpty () {
418+ result .ConstructorArgs = output15 .ConstructorArgs
419+ } else {
420+ // merge the dest chainConfig args
421+ result .ConstructorArgs .DestChainConfigArgs = mergeDestChainConfigs (
422+ result .ConstructorArgs .DestChainConfigArgs ,
423+ output15 .ConstructorArgs .DestChainConfigArgs )
424+ resultPriceUpdatersMap := make (map [common.Address ]bool )
425+ for _ , updater := range result .ConstructorArgs .PriceUpdaters {
426+ resultPriceUpdatersMap [updater ] = true
427+ }
428+ for _ , updater := range output15 .ConstructorArgs .PriceUpdaters {
429+ if ! resultPriceUpdatersMap [updater ] {
430+ result .ConstructorArgs .PriceUpdaters = append (result .ConstructorArgs .PriceUpdaters , updater )
431+ resultPriceUpdatersMap [updater ] = true
432+ }
433+ }
434+ result .ConstructorArgs .TokenTransferFeeConfigArgs = mergeTokenTransferFeeConfigArgs (
435+ result .ConstructorArgs .TokenTransferFeeConfigArgs ,
436+ output15 .ConstructorArgs .TokenTransferFeeConfigArgs )
437+ result .ConstructorArgs .PriceUpdaters = maps .Keys (resultPriceUpdatersMap )
438+ }
439+
440+ result .DestChainConfigs = mergeDestChainConfigs (result .DestChainConfigs , output15 .DestChainConfigs )
441+
442+ // TokenTransferFeeConfigUpdates: merge by DestChainSelector, output16 takes precedence for duplicates
443+ result .TokenTransferFeeConfigUpdates .TokenTransferFeeConfigArgs = mergeTokenTransferFeeConfigArgs (
444+ result .TokenTransferFeeConfigUpdates .TokenTransferFeeConfigArgs ,
445+ output15 .TokenTransferFeeConfigUpdates .TokenTransferFeeConfigArgs )
446+
447+ // TokensToUseDefaultFeeConfigs: merge by DestChainSelector and Token
448+ if len (result .TokenTransferFeeConfigUpdates .TokensToUseDefaultFeeConfigs ) == 0 {
449+ result .TokenTransferFeeConfigUpdates .TokensToUseDefaultFeeConfigs = output15 .TokenTransferFeeConfigUpdates .TokensToUseDefaultFeeConfigs
450+ } else {
451+ // Create a map of (DestChainSelector, Token) pairs from output16
452+ tokenRemoveMap := make (map [string ]bool )
453+ for _ , cfg := range result .TokenTransferFeeConfigUpdates .TokensToUseDefaultFeeConfigs {
454+ key := fmt .Sprintf ("%d:%s" , cfg .DestChainSelector , cfg .Token .Hex ())
455+ tokenRemoveMap [key ] = true
456+ }
457+ // Add configs from output15 that don't exist in output16
458+ for _ , cfg := range output15 .TokenTransferFeeConfigUpdates .TokensToUseDefaultFeeConfigs {
459+ key := fmt .Sprintf ("%d:%s" , cfg .DestChainSelector , cfg .Token .Hex ())
460+ if ! tokenRemoveMap [key ] {
461+ result .TokenTransferFeeConfigUpdates .TokensToUseDefaultFeeConfigs = append (result .TokenTransferFeeConfigUpdates .TokensToUseDefaultFeeConfigs , cfg )
462+ }
463+ // If it exists in both, output16's value is already used (takes precedence)
464+ }
465+ }
466+
467+ // AuthorizedCallerUpdates: merge unique entries from both outputs
468+ result .AuthorizedCallerUpdates = mergePriceUpdaters (result .AuthorizedCallerUpdates , output15 .AuthorizedCallerUpdates )
469+
470+ return result , nil
471+ }
472+
473+ func mergeTokenTransferFeeConfigArgs (args1 , args2 []fee_quoter.FeeQuoterTokenTransferFeeConfigArgs ) []fee_quoter.FeeQuoterTokenTransferFeeConfigArgs {
474+ result := args1
475+ // TokenTransferFeeConfigArgs: merge by DestChainSelector
476+ if len (result ) == 0 {
477+ result = args2
478+ } else {
479+ // Create a map of dest chain selectors from output16
480+ tokenConfigMap := make (map [uint64 ]int )
481+ for i , cfg := range result {
482+ tokenConfigMap [cfg .DestChainSelector ] = i
483+ }
484+ // Add configs from output15 that don't exist in output16
485+ for _ , cfg := range args2 {
486+ if _ , exists := tokenConfigMap [cfg .DestChainSelector ]; ! exists && len (cfg .TokenTransferFeeConfigs ) > 0 {
487+ result = append (result , cfg )
488+ }
489+ // If it exists in both, output16's value is already used (takes precedence)
490+ }
491+ }
492+ return result
493+ }
494+
495+ func mergePriceUpdaters (updaters1 , updaters2 fqops.AuthorizedCallerArgs ) fqops.AuthorizedCallerArgs {
496+ result := updaters1
497+ // AddedCallers: merge unique addresses from both outputs
498+ addedCallersMap := make (map [common.Address ]bool )
499+ for _ , addr := range result .AddedCallers {
500+ addedCallersMap [addr ] = true
501+ }
502+ for _ , addr := range updaters2 .AddedCallers {
503+ if ! addedCallersMap [addr ] {
504+ result .AddedCallers = append (result .AddedCallers , addr )
505+ addedCallersMap [addr ] = true
506+ }
507+ }
508+ // RemovedCallers: merge unique addresses from both outputs
509+ removedCallersMap := make (map [common.Address ]bool )
510+ for _ , addr := range result .RemovedCallers {
511+ removedCallersMap [addr ] = true
512+ }
513+ for _ , addr := range updaters2 .RemovedCallers {
514+ if ! removedCallersMap [addr ] {
515+ result .RemovedCallers = append (result .RemovedCallers , addr )
516+ removedCallersMap [addr ] = true
517+ }
518+ }
519+ return result
520+ }
521+
522+ func mergeDestChainConfigs (cfgs1 , cfgs2 []fqops.DestChainConfigArgs ) []fqops.DestChainConfigArgs {
523+ // Create a map of dest chain selectors from cfgs1
524+ destChainMap := make (map [uint64 ]fqops.DestChainConfigArgs )
525+ for _ , cfg := range cfgs1 {
526+ destChainMap [cfg .DestChainSelector ] = cfg
527+ }
528+ result := cfgs1
529+ // Add configs from cfgs2 that don't exist in cfgs1
530+ for _ , cfg := range cfgs2 {
531+ if _ , exists := destChainMap [cfg .DestChainSelector ]; ! exists {
532+ result = append (result , cfg )
533+ }
534+ // If it exists in both, cfgs1's value is already used (takes precedence)
535+ }
536+ if len (destChainMap ) == 0 {
537+ return nil
538+ }
539+ return result
540+ }
0 commit comments