Skip to content

Commit fcffbb8

Browse files
authored
CCIP Home changeset : merge USDCTokenPoolProxy address from datastore (#21567)
* loadOnchainStateForCandidateChangesets * fix for dup entries * lint fix * move test * use slices.Contains
1 parent 4561cb7 commit fcffbb8

2 files changed

Lines changed: 191 additions & 18 deletions

File tree

deployment/ccip/changeset/v1_6/cs_ccip_home.go

Lines changed: 75 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ import (
66
"errors"
77
"fmt"
88
"math/big"
9+
"slices"
910
"strings"
1011

1112
"github.com/ethereum/go-ethereum/accounts/abi/bind"
1213
"github.com/ethereum/go-ethereum/common"
1314
"github.com/gagliardetto/solana-go"
1415
"golang.org/x/exp/maps"
1516

17+
"github.com/Masterminds/semver/v3"
1618
chain_selectors "github.com/smartcontractkit/chain-selectors"
1719

1820
mcmslib "github.com/smartcontractkit/mcms"
@@ -28,6 +30,7 @@ import (
2830
"github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/tokens"
2931
"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
3032
"github.com/smartcontractkit/chainlink-ccip/pluginconfig"
33+
"github.com/smartcontractkit/chainlink-deployments-framework/datastore"
3134

3235
capabilities_registry "github.com/smartcontractkit/chainlink-evm/gethwrappers/keystone/generated/capabilities_registry_1_1_0"
3336

@@ -185,29 +188,36 @@ func validateUSDCConfig(usdcConfig *pluginconfig.USDCCCTPObserverConfig, state s
185188
if !ok {
186189
return fmt.Errorf("chain %d does not exist in EVM chain state but provided in USDCCCTPObserverConfig", sel)
187190
}
188-
if onchainState.USDCTokenPools == nil && onchainState.USDCTokenPoolsV1_6 == nil && onchainState.USDCTokenPoolProxies == nil {
189-
return fmt.Errorf("chain %d does not have any USDC token pools deployed", sel)
191+
validSourcePools := make([]common.Address, 0, 3)
192+
if pool, ok := onchainState.USDCTokenPools[deployment.Version1_5_1]; ok {
193+
validSourcePools = append(validSourcePools, pool.Address())
194+
}
195+
if pool, ok := onchainState.USDCTokenPoolsV1_6[deployment.Version1_6_2]; ok {
196+
validSourcePools = append(validSourcePools, pool.Address())
190197
}
191-
192-
var sourcePoolAddress common.Address
193198
if proxy, ok := onchainState.USDCTokenPoolProxies[deployment.Version1_7_0]; ok {
194-
sourcePoolAddress = proxy
195-
} else if pool, ok := onchainState.USDCTokenPoolsV1_6[deployment.Version1_6_2]; ok {
196-
sourcePoolAddress = pool.Address()
197-
} else if pool, ok := onchainState.USDCTokenPools[deployment.Version1_5_1]; ok {
198-
sourcePoolAddress = pool.Address()
199-
} else {
199+
validSourcePools = append(validSourcePools, proxy)
200+
}
201+
if len(validSourcePools) == 0 {
200202
return fmt.Errorf(
201203
"chain %d does not have USDC token pool deployed with version %s, %s, or %s",
202204
sel, deployment.Version1_5_1, deployment.Version1_6_2, deployment.Version1_7_0,
203205
)
204206
}
205207

206-
if common.HexToAddress(token.SourcePoolAddress) != sourcePoolAddress {
207-
return fmt.Errorf("chain %d has latest USDC token pool deployed at %s, "+
208-
"but SourcePoolAddress %s is provided in USDCCCTPObserverConfig",
209-
sel, sourcePoolAddress.String(), token.SourcePoolAddress)
208+
configuredSourcePool := common.HexToAddress(token.SourcePoolAddress)
209+
if slices.Contains(validSourcePools, configuredSourcePool) {
210+
break
211+
}
212+
213+
expectedAddresses := make([]string, 0, len(validSourcePools))
214+
for _, sourcePoolAddress := range validSourcePools {
215+
expectedAddresses = append(expectedAddresses, sourcePoolAddress.String())
210216
}
217+
return fmt.Errorf(
218+
"chain %d SourcePoolAddress %s is not one of the deployed USDC pools %v",
219+
sel, token.SourcePoolAddress, expectedAddresses,
220+
)
211221
case chain_selectors.FamilySolana:
212222
onchainState, ok := state.SolChains[uint64(sel)]
213223
if !ok {
@@ -249,6 +259,53 @@ func validateUSDCConfig(usdcConfig *pluginconfig.USDCCCTPObserverConfig, state s
249259
return nil
250260
}
251261

262+
func loadOnchainStateForCandidateChangesets(e cldf.Environment) (stateview.CCIPOnChainState, error) {
263+
state, err := stateview.LoadOnchainState(e)
264+
if err != nil {
265+
return stateview.CCIPOnChainState{}, err
266+
}
267+
if e.DataStore == nil {
268+
return state, nil
269+
}
270+
271+
for chainSelector := range e.BlockChains.EVMChains() {
272+
refs := e.DataStore.Addresses().Filter(
273+
datastore.AddressRefByChainSelector(chainSelector),
274+
datastore.AddressRefByType(datastore.ContractType(shared.USDCTokenPoolProxy)),
275+
datastore.AddressRefByVersion(&deployment.Version1_7_0),
276+
)
277+
if len(refs) == 0 {
278+
continue
279+
}
280+
if len(refs) > 1 {
281+
return stateview.CCIPOnChainState{}, fmt.Errorf(
282+
"multiple datastore entries found for %s %s on chain %d; qualifiers=%v",
283+
shared.USDCTokenPoolProxy, deployment.Version1_7_0, chainSelector, maps.Keys(refsByQualifier(refs)),
284+
)
285+
}
286+
287+
chainState, ok := state.EVMChainState(chainSelector)
288+
if !ok {
289+
return stateview.CCIPOnChainState{}, fmt.Errorf("chain %d not found in state", chainSelector)
290+
}
291+
if chainState.USDCTokenPoolProxies == nil {
292+
chainState.USDCTokenPoolProxies = make(map[semver.Version]common.Address)
293+
}
294+
chainState.USDCTokenPoolProxies[deployment.Version1_7_0] = common.HexToAddress(refs[0].Address)
295+
state.WriteEVMChainState(chainSelector, chainState)
296+
}
297+
298+
return state, state.Validate()
299+
}
300+
301+
func refsByQualifier(refs []datastore.AddressRef) map[string]struct{} {
302+
out := make(map[string]struct{}, len(refs))
303+
for _, ref := range refs {
304+
out[ref.Qualifier] = struct{}{}
305+
}
306+
return out
307+
}
308+
252309
type CCIPOCRParams struct {
253310
// OCRParameters contains the parameters for the OCR plugin.
254311
OCRParameters commontypes.OCRParameters `json:"ocrParameters"`
@@ -312,7 +369,7 @@ type PromoteCandidateChangesetConfig struct {
312369
}
313370

314371
func (p PromoteCandidateChangesetConfig) Validate(e cldf.Environment) (map[uint64]uint32, error) {
315-
state, err := stateview.LoadOnchainState(e)
372+
state, err := loadOnchainStateForCandidateChangesets(e)
316373
if err != nil {
317374
return nil, err
318375
}
@@ -398,7 +455,7 @@ func PromoteCandidateChangeset(
398455
if err != nil {
399456
return cldf.ChangesetOutput{}, fmt.Errorf("%w: %w", cldf.ErrInvalidConfig, err)
400457
}
401-
state, err := stateview.LoadOnchainState(e)
458+
state, err := loadOnchainStateForCandidateChangesets(e)
402459
if err != nil {
403460
return cldf.ChangesetOutput{}, err
404461
}
@@ -630,7 +687,7 @@ func AddDonAndSetCandidateChangeset(
630687
e cldf.Environment,
631688
cfg AddDonAndSetCandidateChangesetConfig,
632689
) (cldf.ChangesetOutput, error) {
633-
state, err := stateview.LoadOnchainState(e)
690+
state, err := loadOnchainStateForCandidateChangesets(e)
634691
if err != nil {
635692
return cldf.ChangesetOutput{}, err
636693
}
@@ -763,7 +820,7 @@ func SetCandidateChangeset(
763820
e cldf.Environment,
764821
cfg SetCandidateChangesetConfig,
765822
) (cldf.ChangesetOutput, error) {
766-
state, err := stateview.LoadOnchainState(e)
823+
state, err := loadOnchainStateForCandidateChangesets(e)
767824
if err != nil {
768825
return cldf.ChangesetOutput{}, err
769826
}

deployment/ccip/changeset/v1_6/cs_ccip_home_test.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ import (
77
"time"
88

99
"github.com/ethereum/go-ethereum/accounts/abi/bind"
10+
"github.com/ethereum/go-ethereum/common"
1011
chain_selectors "github.com/smartcontractkit/chain-selectors"
1112
"github.com/stretchr/testify/assert"
1213
"golang.org/x/exp/maps"
1314

15+
"github.com/smartcontractkit/chainlink-ccip/pluginconfig"
16+
"github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3"
1417
cldf_chain "github.com/smartcontractkit/chainlink-deployments-framework/chain"
18+
"github.com/smartcontractkit/chainlink-deployments-framework/datastore"
1519

1620
"github.com/smartcontractkit/chainlink-ccip/chainconfig"
1721
cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
@@ -549,3 +553,115 @@ func Test_UpdateChainConfigs(t *testing.T) {
549553
})
550554
}
551555
}
556+
557+
func Test_SetCandidateAcceptsUSDCTokenPoolProxyFromDataStore(t *testing.T) {
558+
t.Parallel()
559+
560+
tenv, _ := testhelpers.NewMemoryEnvironment(t,
561+
testhelpers.WithNumOfChains(2),
562+
testhelpers.WithNumOfNodes(4))
563+
dest := remoteChainSelector(
564+
tenv.Env.BlockChains.ListChainSelectors(cldf_chain.WithFamily(chain_selectors.FamilyEVM)),
565+
tenv.HomeChainSel,
566+
)
567+
proxyAddress := common.HexToAddress("0x1000000000000000000000000000000000000001")
568+
569+
ds := datastore.NewMemoryDataStore()
570+
require.NoError(t, ds.Addresses().Add(datastore.AddressRef{
571+
ChainSelector: dest,
572+
Address: proxyAddress.Hex(),
573+
Type: datastore.ContractType(shared.USDCTokenPoolProxy),
574+
Version: &deployment.Version1_7_0,
575+
Qualifier: "proxy-only-in-datastore",
576+
}))
577+
tenv.Env.DataStore = ds.Seal()
578+
579+
_, err := commonchangeset.Apply(t, tenv.Env,
580+
commonchangeset.Configure(
581+
cldf.CreateLegacyChangeSet(v1_6.SetCandidateChangeset),
582+
setCandidateExecConfig(tenv.HomeChainSel, tenv.FeedChainSel, dest, proxyAddress.Hex()),
583+
),
584+
)
585+
require.NoError(t, err)
586+
}
587+
588+
func Test_SetCandidateErrorsOnDuplicateUSDCTokenPoolProxyInDataStore(t *testing.T) {
589+
t.Parallel()
590+
591+
tenv, _ := testhelpers.NewMemoryEnvironment(t,
592+
testhelpers.WithNumOfChains(2),
593+
testhelpers.WithNumOfNodes(4))
594+
dest := remoteChainSelector(
595+
tenv.Env.BlockChains.ListChainSelectors(cldf_chain.WithFamily(chain_selectors.FamilyEVM)),
596+
tenv.HomeChainSel,
597+
)
598+
599+
ds := datastore.NewMemoryDataStore()
600+
for i, ref := range []struct {
601+
address string
602+
qualifier string
603+
}{
604+
{address: "0x1000000000000000000000000000000000000001", qualifier: "proxy-a"},
605+
{address: "0x2000000000000000000000000000000000000002", qualifier: "proxy-b"},
606+
} {
607+
require.NoError(t, ds.Addresses().Add(datastore.AddressRef{
608+
ChainSelector: dest,
609+
Address: common.HexToAddress(ref.address).Hex(),
610+
Type: datastore.ContractType(shared.USDCTokenPoolProxy),
611+
Version: &deployment.Version1_7_0,
612+
Qualifier: ref.qualifier,
613+
}), "add datastore ref %d", i)
614+
}
615+
tenv.Env.DataStore = ds.Seal()
616+
617+
_, err := commonchangeset.Apply(t, tenv.Env,
618+
commonchangeset.Configure(
619+
cldf.CreateLegacyChangeSet(v1_6.SetCandidateChangeset),
620+
setCandidateExecConfig(tenv.HomeChainSel, tenv.FeedChainSel, dest, "0x1000000000000000000000000000000000000001"),
621+
),
622+
)
623+
require.Error(t, err)
624+
require.ErrorContains(t, err, "multiple datastore entries found for USDCTokenPoolProxy 1.7.0")
625+
}
626+
627+
func setCandidateExecConfig(homeChainSel, feedChainSel, dest uint64, sourcePoolAddress string) v1_6.SetCandidateChangesetConfig {
628+
return v1_6.SetCandidateChangesetConfig{
629+
SetCandidateConfigBase: v1_6.SetCandidateConfigBase{
630+
HomeChainSelector: homeChainSel,
631+
FeedChainSelector: feedChainSel,
632+
},
633+
PluginInfo: []v1_6.SetCandidatePluginInfo{
634+
{
635+
PluginType: types.PluginTypeCCIPExec,
636+
OCRConfigPerRemoteChainSelector: map[uint64]v1_6.CCIPOCRParams{
637+
dest: v1_6.DeriveOCRParamsForExec(v1_6.SimulationTest, []pluginconfig.TokenDataObserverConfig{
638+
{
639+
Type: pluginconfig.USDCCCTPHandlerType,
640+
Version: "1.0",
641+
USDCCCTPObserverConfig: &pluginconfig.USDCCCTPObserverConfig{
642+
AttestationConfig: pluginconfig.AttestationConfig{
643+
AttestationAPI: "http://example.com",
644+
},
645+
Tokens: map[ccipocr3.ChainSelector]pluginconfig.USDCCCTPTokenConfig{
646+
ccipocr3.ChainSelector(dest): {
647+
SourcePoolAddress: sourcePoolAddress,
648+
SourceMessageTransmitterAddr: common.HexToAddress("0x3000000000000000000000000000000000000003").Hex(),
649+
},
650+
},
651+
},
652+
},
653+
}, nil),
654+
},
655+
},
656+
},
657+
}
658+
}
659+
660+
func remoteChainSelector(selectors []uint64, exclude uint64) uint64 {
661+
for _, selector := range selectors {
662+
if selector != exclude {
663+
return selector
664+
}
665+
}
666+
return 0
667+
}

0 commit comments

Comments
 (0)