Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions build/devenv/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,9 +397,9 @@ func qualifiersAvailable(qualifiers []string, topology *offchain.EnvironmentTopo
}

// FilterTokenCombinations returns only the token combinations whose CCV qualifiers
// all exist as committees in the topology, and when ds is non-nil, whose local and
// remote pool address refs exist in ds for every selector (each chain deploys both
// pools across bidirectional transfer configs).
// all exist as committees in the topology. When ds is non-nil, selectors[0] is treated
// as the local chain and selectors[1:] as candidate remotes, and the filter keeps only
// combinations whose declared local->remote orientation exists in the datastore.
// Pass ds nil to skip the datastore check.
func FilterTokenCombinations(combos []TokenCombination, topology *offchain.EnvironmentTopology, ds datastore.DataStore, selectors []uint64) []TokenCombination {
filtered := make([]TokenCombination, 0, len(combos))
Expand All @@ -417,14 +417,21 @@ func FilterTokenCombinations(combos []TokenCombination, topology *offchain.Envir
}

func tokenCombinationPoolsExistInDataStore(ds datastore.DataStore, selectors []uint64, combo TokenCombination) bool {
local := combo.LocalPoolAddressRef()
remote := combo.RemotePoolAddressRef()
for _, sel := range selectors {
if !dataStoreHasAddressRef(ds, sel, local) || !dataStoreHasAddressRef(ds, sel, remote) {
return false
if len(selectors) == 0 {
return true
}
if !dataStoreHasAddressRef(ds, selectors[0], combo.LocalPoolAddressRef()) {
return false
}
// A combo is usable for a selector if that selector has the local pool and at
// least one remote selector has the corresponding remote pool. Requiring the
// same remote pool to exist on every remote selector drops valid mixed lanes.
for _, sel := range selectors[1:] {
if dataStoreHasAddressRef(ds, sel, combo.RemotePoolAddressRef()) {
return true
}
}
return true
return len(selectors) == 1
}

func dataStoreHasAddressRef(ds datastore.DataStore, chainSelector uint64, ref datastore.AddressRef) bool {
Expand Down
63 changes: 40 additions & 23 deletions build/devenv/evm/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -1321,25 +1321,38 @@ func (m *CCIP17EVMConfig) GetTokenTransferConfigs(
applicableCombos := devenvcommon.FilterTokenCombinations(
devenvcommon.AllTokenCombinations(), topology, env.DataStore, append([]uint64{selector}, remoteSelectors...),
)
hasAddressRef := func(chainSelector uint64, ref datastore.AddressRef) bool {
_, err := env.DataStore.Addresses().Get(datastore.NewAddressRefKey(
chainSelector,
ref.Type,
ref.Version,
ref.Qualifier,
))
return err == nil
}
merged := make(map[string]tokenscore.TokenTransferConfig)

for _, combo := range applicableCombos {
for _, pair := range []struct {
local, remote datastore.AddressRef
ccvQuals []string
}{
{combo.LocalPoolAddressRef(), combo.RemotePoolAddressRef(), combo.LocalPoolCCVQualifiers()},
{combo.RemotePoolAddressRef(), combo.LocalPoolAddressRef(), combo.RemotePoolCCVQualifiers()},
} {
cfg := m.buildEVMTokenTransferConfig(selector, remoteSelectors, pair.local, pair.remote, pair.ccvQuals)
key := string(cfg.TokenPoolRef.Type) + "\x00" + cfg.TokenPoolRef.Version.String() + "\x00" + cfg.TokenPoolRef.Qualifier
if existing, ok := merged[key]; ok {
maps.Copy(existing.RemoteChains, cfg.RemoteChains)
merged[key] = existing
} else {
merged[key] = cfg
eligibleRemoteSelectors := make([]uint64, 0, len(remoteSelectors))
// Emit only the remote selectors that actually have the pool required by
// this combo. ConfigureTokensForTransfers expects every advertised remote
// to have a reciprocal config on the other side.
for _, rs := range remoteSelectors {
if hasAddressRef(rs, combo.RemotePoolAddressRef()) {
eligibleRemoteSelectors = append(eligibleRemoteSelectors, rs)
}
}
if len(eligibleRemoteSelectors) == 0 {
continue
}
cfg := m.buildEVMTokenTransferConfig(selector, eligibleRemoteSelectors, combo.LocalPoolAddressRef(), combo.RemotePoolAddressRef(), combo.LocalPoolCCVQualifiers())
key := string(cfg.TokenPoolRef.Type) + "\x00" + cfg.TokenPoolRef.Version.String() + "\x00" + cfg.TokenPoolRef.Qualifier
if existing, ok := merged[key]; ok {
maps.Copy(existing.RemoteChains, cfg.RemoteChains)
merged[key] = existing
} else {
merged[key] = cfg
}
}

configs := make([]tokenscore.TokenTransferConfig, 0, len(merged))
Expand All @@ -1357,16 +1370,15 @@ func (m *CCIP17EVMConfig) buildEVMTokenTransferConfig(
ccvQualifiers []string,
) tokenscore.TokenTransferConfig {
remoteChains := make(map[uint64]tokenscore.RemoteChainConfig[*datastore.AddressRef, datastore.AddressRef])
ccvRefs := make([]datastore.AddressRef, 0, len(ccvQualifiers))
for _, qualifier := range ccvQualifiers {
ccvRefs = append(ccvRefs, datastore.AddressRef{
Type: datastore.ContractType(versioned_verifier_resolver.CommitteeVerifierResolverType),
Version: versioned_verifier_resolver.Version,
Qualifier: qualifier,
})
}
for _, rs := range remoteSelectors {
ccvRefs := make([]datastore.AddressRef, 0, len(ccvQualifiers))
for _, qualifier := range ccvQualifiers {
ccvRefs = append(ccvRefs, datastore.AddressRef{
Type: datastore.ContractType(versioned_verifier_resolver.CommitteeVerifierResolverType),
Version: versioned_verifier_resolver.Version,
Qualifier: qualifier,
})
}

remoteChains[rs] = tokenscore.RemoteChainConfig[*datastore.AddressRef, datastore.AddressRef]{
RemotePool: &remoteRef,
DefaultFinalityInboundRateLimiterConfig: tokenscore.RateLimiterConfigFloatInput{},
Expand All @@ -1381,6 +1393,11 @@ func (m *CCIP17EVMConfig) buildEVMTokenTransferConfig(
return tokenscore.TokenTransferConfig{
ChainSelector: selector,
TokenPoolRef: localRef,
TokenRef: datastore.AddressRef{
Type: datastore.ContractType(bnm_drip_v1_0.ContractType),
Version: semver.MustParse(bnm_drip_v1_0.Deploy.Version()),
Qualifier: localRef.Qualifier,
},
RegistryRef: datastore.AddressRef{
Type: datastore.ContractType(token_admin_registry.ContractType),
Version: semver.MustParse(token_admin_registry.Deploy.Version()),
Expand Down
71 changes: 60 additions & 11 deletions build/devenv/implcommon.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ccv
import (
"context"
"fmt"
"maps"
"sort"

"github.com/Masterminds/semver/v3"
Expand Down Expand Up @@ -526,17 +527,30 @@ func ConfigureAllTokenTransfers(
env *deployment.Environment,
topology *ccipOffchain.EnvironmentTopology,
) error {
// poolIdentityKey returns a key that groups configs across chains for the
// same pool type+version+qualifier.
poolIdentityKey := func(cfg *tokenscore.TokenTransferConfig) string {
refKey := func(ref datastore.AddressRef) string {
v := ""
if cfg.TokenPoolRef.Version != nil {
v = cfg.TokenPoolRef.Version.String()
if ref.Version != nil {
v = ref.Version.String()
}
return string(cfg.TokenPoolRef.Type) + "+" + v + "+" + cfg.TokenPoolRef.Qualifier
return string(ref.Type) + "+" + v + "+" + ref.Qualifier
}

byPoolIdentity := make(map[string][]tokenscore.TokenTransferConfig)
// laneKey groups reciprocal configs for the same selector pair by the local
// pool identity each selector contributes. The selector ordering is stable,
// so A(local burn)->B(remote lock) and B(local lock)->A(remote burn) land in
// the same bucket, while the opposite orientation on the same selector pair
// stays distinct.
laneKey := func(local datastore.AddressRef, localSelector uint64, remote datastore.AddressRef, remoteSelector uint64) string {
leftSelector, leftRef := localSelector, local
rightSelector, rightRef := remoteSelector, remote
if leftSelector > rightSelector {
leftSelector, rightSelector = rightSelector, leftSelector
leftRef, rightRef = rightRef, leftRef
}
return fmt.Sprintf("%d:%s<->%d:%s", leftSelector, refKey(leftRef), rightSelector, refKey(rightRef))
}

byLane := make(map[string]map[uint64]tokenscore.TokenTransferConfig)

for i, impl := range impls {
tcp, ok := impl.(cciptestinterfaces.TokenConfigProvider)
Expand All @@ -555,18 +569,53 @@ func ConfigureAllTokenTransfers(
return fmt.Errorf("get token transfer configs for selector %d: %w", selectors[i], err)
}
for _, cfg := range cfgs {
key := poolIdentityKey(&cfg)
byPoolIdentity[key] = append(byPoolIdentity[key], cfg)
for remoteSelector, remoteCfg := range cfg.RemoteChains {
if remoteCfg.RemotePool == nil {
continue
}

key := laneKey(cfg.TokenPoolRef, cfg.ChainSelector, *remoteCfg.RemotePool, remoteSelector)
splitCfg := cfg
splitCfg.RemoteChains = map[uint64]tokenscore.RemoteChainConfig[*datastore.AddressRef, datastore.AddressRef]{
remoteSelector: remoteCfg,
}

if byLane[key] == nil {
byLane[key] = make(map[uint64]tokenscore.TokenTransferConfig)
}

if existing, ok := byLane[key][cfg.ChainSelector]; ok {
if refKey(existing.TokenPoolRef) != refKey(splitCfg.TokenPoolRef) {
return fmt.Errorf(
"selector %d produced conflicting local pool configs for lane %s: %s/%s vs %s/%s",
cfg.ChainSelector,
key,
existing.TokenPoolRef.Type,
existing.TokenPoolRef.Qualifier,
splitCfg.TokenPoolRef.Type,
splitCfg.TokenPoolRef.Qualifier,
)
}
maps.Copy(existing.RemoteChains, splitCfg.RemoteChains)
byLane[key][cfg.ChainSelector] = existing
} else {
byLane[key][cfg.ChainSelector] = splitCfg
}
}
}
}

if len(byPoolIdentity) == 0 {
if len(byLane) == 0 {
return nil
}

tokenAdapterRegistry := tokenscore.GetTokenAdapterRegistry()
mcmsReaderRegistry := changesetscore.GetRegistry()
for _, group := range byPoolIdentity {
for _, groupedBySelector := range byLane {
group := make([]tokenscore.TokenTransferConfig, 0, len(groupedBySelector))
for _, cfg := range groupedBySelector {
group = append(group, cfg)
}
_, err := tokenscore.ConfigureTokensForTransfers(tokenAdapterRegistry, mcmsReaderRegistry).Apply(*env, tokenscore.ConfigureTokensForTransfersConfig{
Tokens: group,
})
Expand Down
Loading