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
557 changes: 543 additions & 14 deletions deployment/ccip/changeset/cs_orchestrate_changesets.go

Large diffs are not rendered by default.

536 changes: 536 additions & 0 deletions deployment/ccip/changeset/merge_test.go

Large diffs are not rendered by default.

30 changes: 20 additions & 10 deletions deployment/ccip/changeset/solana_v0_1_0/cs_e2e.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
cldfproposalutils "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/mcms/proposalutils"

"github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/v1_5_1"
"github.com/smartcontractkit/chainlink/deployment/ccip/shared"
"github.com/smartcontractkit/chainlink/deployment/ccip/shared/stateview"
Expand Down Expand Up @@ -98,7 +99,7 @@ func ProcessConfig[T any](
if err != nil {
return err
}
err = cldf.MergeChangesetOutput(*e, finalOutput, output)
err = changeset.MergeChangesetOutput(*e, finalOutput, output)
if err != nil {
return fmt.Errorf("failed to merge changeset output: %w", err)
}
Expand Down Expand Up @@ -321,42 +322,42 @@ func E2ETokenPoolv2(env cldf.Environment, cfg E2ETokenPoolConfigv2) (cldf.Change
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to add token pool and lookup table: %w", err)
}
if err = cldf.MergeChangesetOutput(e, finalCSOut, output); err != nil {
if err = changeset.MergeChangesetOutput(e, finalCSOut, output); err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge changeset output after running AddTokenPoolAndLookupTable: %w", err)
}
output, err = SetupTokenPoolForRemoteChain(e, remotePoolConfig)
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to setup token pool for remote chain: %w", err)
}
if err = cldf.MergeChangesetOutput(e, finalCSOut, output); err != nil {
if err = changeset.MergeChangesetOutput(e, finalCSOut, output); err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge changeset output after running SetupTokenPoolForRemoteChain: %w", err)
}
output, err = RegisterTokenAdminRegistry(e, registerTokenAdminRegistryCfg)
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to register token admin registry: %w", err)
}
if err = cldf.MergeChangesetOutput(e, finalCSOut, output); err != nil {
if err = changeset.MergeChangesetOutput(e, finalCSOut, output); err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge changeset output after running RegisterTokenAdminRegistry: %w", err)
}
output, err = AcceptAdminRoleTokenAdminRegistry(e, acceptAdminRoleTokenAdminRegistryCfg)
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to accept admin role: %w", err)
}
if err = cldf.MergeChangesetOutput(e, finalCSOut, output); err != nil {
if err = changeset.MergeChangesetOutput(e, finalCSOut, output); err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge changeset output after running AcceptAdminRoleTokenAdminRegistry: %w", err)
}
output, err = SetPool(e, setPoolCfg)
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to set pool: %w", err)
}
if err = cldf.MergeChangesetOutput(e, finalCSOut, output); err != nil {
if err = changeset.MergeChangesetOutput(e, finalCSOut, output); err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge changeset output after running SetPool: %w", err)
}
output, err = v1_5_1.ConfigureMultiplePoolLogic(e, evmToSolanaRemotePoolCfg)
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to configure token pool contracts: %w", err)
}
if err = cldf.MergeChangesetOutput(e, finalCSOut, output); err != nil {
if err = changeset.MergeChangesetOutput(e, finalCSOut, output); err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge changeset output after running ConfigureTokenPoolContractsChangeset: %w", err)
}
// and finally lets transfer away the pool to timelock
Expand All @@ -365,7 +366,7 @@ func E2ETokenPoolv2(env cldf.Environment, cfg E2ETokenPoolConfigv2) (cldf.Change
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to transfer ccip to mcms with timelock: %w", err)
}
if err = cldf.MergeChangesetOutput(e, finalCSOut, output); err != nil {
if err = changeset.MergeChangesetOutput(e, finalCSOut, output); err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge changeset output after running TransferCCIPToMCMSWithTimelockSolana: %w", err)
}
}
Expand All @@ -390,11 +391,20 @@ func E2ETokenPoolv2(env cldf.Environment, cfg E2ETokenPoolConfigv2) (cldf.Change
}
}

ds, err := shared.PopulateDataStore(finalCSOut.AddressBook) //nolint:staticcheck //SA1019 ignoring deprecated
// Preserve the datastore accumulated by MergeChangesetOutput: it already carries the
// sub-changesets' semantic qualifiers, metadata, and deletions. Rebuilding it from the
// address book would discard all of that. The address book has no qualifiers, so it can only
// contribute the singleton refs the sub-changesets did not write to the datastore; merge
// those in rather than replacing the accumulated store.
singletons, err := shared.PopulateDataStore(finalCSOut.AddressBook) //nolint:staticcheck //SA1019 ignoring deprecated
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to populate in-memory DataStore: %w", err)
}

finalCSOut.DataStore = ds
if finalCSOut.DataStore == nil {
finalCSOut.DataStore = singletons
} else if err := finalCSOut.DataStore.Merge(singletons.Seal()); err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge address-book refs into DataStore: %w", err)
}
return *finalCSOut, nil
}
32 changes: 21 additions & 11 deletions deployment/ccip/changeset/solana_v0_1_1/cs_e2e.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
cldfproposalutils "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/mcms/proposalutils"

"github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/v1_5_1"
"github.com/smartcontractkit/chainlink/deployment/ccip/shared"
"github.com/smartcontractkit/chainlink/deployment/ccip/shared/stateview"
Expand Down Expand Up @@ -104,7 +105,7 @@ func ProcessConfig[T any](
if err != nil {
return err
}
err = cldf.MergeChangesetOutput(*e, finalOutput, output)
err = changeset.MergeChangesetOutput(*e, finalOutput, output)
if err != nil {
return fmt.Errorf("failed to merge changeset output: %w", err)
}
Expand Down Expand Up @@ -342,50 +343,50 @@ func E2ETokenPoolv2(env cldf.Environment, cfg E2ETokenPoolConfigv2) (cldf.Change
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to initialize global config for token pool: %w", err)
}
if err = cldf.MergeChangesetOutput(e, finalCSOut, output); err != nil {
if err = changeset.MergeChangesetOutput(e, finalCSOut, output); err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge changeset output after running InitGlobalConfigTokenPoolProgram: %w", err)
}
}
output, err := AddTokenPoolAndLookupTable(e, tokenPoolAndLookupTableCfg)
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to add token pool and lookup table: %w", err)
}
if err = cldf.MergeChangesetOutput(e, finalCSOut, output); err != nil {
if err = changeset.MergeChangesetOutput(e, finalCSOut, output); err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge changeset output after running AddTokenPoolAndLookupTable: %w", err)
}
output, err = SetupTokenPoolForRemoteChain(e, remotePoolConfig)
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to setup token pool for remote chain: %w", err)
}
if err = cldf.MergeChangesetOutput(e, finalCSOut, output); err != nil {
if err = changeset.MergeChangesetOutput(e, finalCSOut, output); err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge changeset output after running SetupTokenPoolForRemoteChain: %w", err)
}
output, err = RegisterTokenAdminRegistry(e, registerTokenAdminRegistryCfg)
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to register token admin registry: %w", err)
}
if err = cldf.MergeChangesetOutput(e, finalCSOut, output); err != nil {
if err = changeset.MergeChangesetOutput(e, finalCSOut, output); err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge changeset output after running RegisterTokenAdminRegistry: %w", err)
}
output, err = AcceptAdminRoleTokenAdminRegistry(e, acceptAdminRoleTokenAdminRegistryCfg)
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to accept admin role: %w", err)
}
if err = cldf.MergeChangesetOutput(e, finalCSOut, output); err != nil {
if err = changeset.MergeChangesetOutput(e, finalCSOut, output); err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge changeset output after running AcceptAdminRoleTokenAdminRegistry: %w", err)
}
output, err = SetPool(e, setPoolCfg)
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to set pool: %w", err)
}
if err = cldf.MergeChangesetOutput(e, finalCSOut, output); err != nil {
if err = changeset.MergeChangesetOutput(e, finalCSOut, output); err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge changeset output after running SetPool: %w", err)
}
output, err = v1_5_1.ConfigureMultiplePoolLogic(e, evmToSolanaRemotePoolCfg)
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to configure token pool contracts: %w", err)
}
if err = cldf.MergeChangesetOutput(e, finalCSOut, output); err != nil {
if err = changeset.MergeChangesetOutput(e, finalCSOut, output); err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge changeset output after running ConfigureTokenPoolContractsChangeset: %w", err)
}
// and finally lets transfer away the pool to timelock
Expand All @@ -394,7 +395,7 @@ func E2ETokenPoolv2(env cldf.Environment, cfg E2ETokenPoolConfigv2) (cldf.Change
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to transfer ccip to mcms with timelock: %w", err)
}
if err = cldf.MergeChangesetOutput(e, finalCSOut, output); err != nil {
if err = changeset.MergeChangesetOutput(e, finalCSOut, output); err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge changeset output after running TransferCCIPToMCMSWithTimelockSolana: %w", err)
}
}
Expand All @@ -419,11 +420,20 @@ func E2ETokenPoolv2(env cldf.Environment, cfg E2ETokenPoolConfigv2) (cldf.Change
}
}

ds, err := shared.PopulateDataStore(finalCSOut.AddressBook) //nolint:staticcheck //SA1019 ignoring deprecated
// Preserve the datastore accumulated by MergeChangesetOutput: it already carries the
// sub-changesets' semantic qualifiers, metadata, and deletions. Rebuilding it from the
// address book would discard all of that. The address book has no qualifiers, so it can only
// contribute the singleton refs the sub-changesets did not write to the datastore; merge
// those in rather than replacing the accumulated store.
singletons, err := shared.PopulateDataStore(finalCSOut.AddressBook) //nolint:staticcheck //SA1019 ignoring deprecated
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to populate in-memory DataStore: %w", err)
}

finalCSOut.DataStore = ds
if finalCSOut.DataStore == nil {
finalCSOut.DataStore = singletons
} else if err := finalCSOut.DataStore.Merge(singletons.Seal()); err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge address-book refs into DataStore: %w", err)
}
return *finalCSOut, nil
}
26 changes: 18 additions & 8 deletions deployment/ccip/changeset/v1_5_1/cs_add_token_e2e.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/smartcontractkit/chainlink-deployments-framework/operations"

"github.com/smartcontractkit/chainlink/deployment"
ccipcommoncs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
"github.com/smartcontractkit/chainlink/deployment/ccip/internal/opsutils"
ccipops "github.com/smartcontractkit/chainlink/deployment/ccip/operation/evm"
"github.com/smartcontractkit/chainlink/deployment/ccip/shared"
Expand Down Expand Up @@ -332,7 +333,7 @@ func addTokenE2ELogic(env cldf.Environment, config AddTokensE2EConfig) (cldf.Cha
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to deploy token pool for token %s: %w", token, err)
}
if err := cldf.MergeChangesetOutput(e, finalCSOut, output); err != nil {
if err := ccipcommoncs.MergeChangesetOutput(e, finalCSOut, output); err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge address book for token %s: %w", token, err)
}
newAddresses, err := output.AddressBook.Addresses() //nolint:staticcheck // Addressbook is deprecated, but we still use it for the time being
Expand Down Expand Up @@ -366,7 +367,7 @@ func addTokenE2ELogic(env cldf.Environment, config AddTokensE2EConfig) (cldf.Cha
return cldf.ChangesetOutput{}, fmt.Errorf("failed to run TransferToMCMSWithTimelock on chain with selector %d: %w", chainID, err)
}

if err := cldf.MergeChangesetOutput(e, finalCSOut, transferOwnershipProposalOutput); err != nil {
if err := ccipcommoncs.MergeChangesetOutput(e, finalCSOut, transferOwnershipProposalOutput); err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge changeset output after transferring ownership of pool(s): %w", err)
}
}
Expand All @@ -382,7 +383,7 @@ func addTokenE2ELogic(env cldf.Environment, config AddTokensE2EConfig) (cldf.Cha
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to propose admin role for token %s: %w", token, err)
}
if err := cldf.MergeChangesetOutput(e, finalCSOut, output); err != nil {
if err := ccipcommoncs.MergeChangesetOutput(e, finalCSOut, output); err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to changeset output after configuring token admin reg for token %s: %w",
token, err)
}
Expand Down Expand Up @@ -414,15 +415,15 @@ func addTokenE2ELogic(env cldf.Environment, config AddTokensE2EConfig) (cldf.Cha
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to accept admin role for token %s: %w", token, err)
}
if err := cldf.MergeChangesetOutput(e, finalCSOut, output); err != nil {
if err := ccipcommoncs.MergeChangesetOutput(e, finalCSOut, output); err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge address book for token %s: %w", token, err)
}
e.Logger.Infow("accepted admin role", "token", token, "config", updatedConfigureTokenAdminReg)
output, err = SetPoolChangeset(e, updatedConfigureTokenAdminReg)
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to set pool for token %s: %w", token, err)
}
if err := cldf.MergeChangesetOutput(e, finalCSOut, output); err != nil {
if err := ccipcommoncs.MergeChangesetOutput(e, finalCSOut, output); err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge address book for token %s: %w", token, err)
}
e.Logger.Infow("set pool", "token", token, "config", updatedConfigureTokenAdminReg)
Expand All @@ -444,7 +445,7 @@ func addTokenE2ELogic(env cldf.Environment, config AddTokensE2EConfig) (cldf.Cha
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to configure token pool for token %s: %w", token, err)
}
if err := cldf.MergeChangesetOutput(e, finalCSOut, output); err != nil {
if err := ccipcommoncs.MergeChangesetOutput(e, finalCSOut, output); err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge changeset output after configuring token pool for token %s: %w", token, err)
}
e.Logger.Infow("configured token pool", "token", token)
Expand All @@ -465,12 +466,21 @@ func addTokenE2ELogic(env cldf.Environment, config AddTokensE2EConfig) (cldf.Cha
finalCSOut.MCMSTimelockProposals = []mcms.TimelockProposal{*aggregatedProposals}
}

ds, err := shared.PopulateDataStore(finalCSOut.AddressBook) //nolint:staticcheck //SA1019 ignoring deprecated
// Preserve the datastore accumulated by MergeChangesetOutput: it already carries the
// sub-changesets' semantic qualifiers, metadata, and deletions. Rebuilding it from the
// address book would discard all of that. The address book has no qualifiers, so it can only
// contribute the singleton refs the sub-changesets did not write to the datastore; merge
// those in rather than replacing the accumulated store.
singletons, err := shared.PopulateDataStore(finalCSOut.AddressBook) //nolint:staticcheck //SA1019 ignoring deprecated
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to populate in-memory DataStore: %w", err)
}

finalCSOut.DataStore = ds
if finalCSOut.DataStore == nil {
finalCSOut.DataStore = singletons
} else if err := finalCSOut.DataStore.Merge(singletons.Seal()); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Skip legacy refs already represented in the datastore

When a sub-changeset records an address using a semantic qualifier, its legacy address-book entry is also passed to PopulateDataStore, which creates another ref for the same contract using the synthetic address-type qualifier. Merging all of those refs unconditionally leaves two keys for the same chain/type/version/address, so lookups that omit a qualifier become ambiguous and the returned datastore no longer preserves the intended write model. Filter address-book-derived refs whose contract is already represented; the same unconditional merge appears in both Solana E2E variants.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Last PR removes this completely. No need to address individually.

return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge address-book refs into DataStore: %w", err)
}
return *finalCSOut, nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
cldfproposalutils "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/mcms/proposalutils"

ccipcommoncs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
"github.com/smartcontractkit/chainlink/deployment/ccip/shared"
"github.com/smartcontractkit/chainlink/deployment/ccip/shared/deployergroup"
"github.com/smartcontractkit/chainlink/deployment/ccip/shared/stateview"
Expand Down Expand Up @@ -1035,7 +1036,7 @@ func ConfigureMultiplePoolLogic(env cldf.Environment, c ConfigureMultipleTokenPo
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to configure token pool: %w", err)
}
err = cldf.MergeChangesetOutput(env, &finalOutput, output)
err = ccipcommoncs.MergeChangesetOutput(env, &finalOutput, output)
if err != nil {
return cldf.ChangesetOutput{}, fmt.Errorf("failed to merge changeset output: %w", err)
}
Expand Down
31 changes: 31 additions & 0 deletions deployment/ccip/shared/addresses.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package shared

import (
"strings"

chainsel "github.com/smartcontractkit/chain-selectors"
)

// NormalizeAddress lower-cases an address only on families where case carries no meaning.
//
// EVM addresses are hex and are commonly written in EIP-55 checksum form, so the same contract
// can appear in two casings and must compare equal. Solana and Sui addresses are case-sensitive,
// where folding case would map two different contracts onto one value. An unknown selector is
// left alone: preserving a distinction that does not exist is harmless, whereas erasing one that
// does silently merges two contracts.
func NormalizeAddress(chainSelector uint64, address string) string {
family, err := chainsel.GetSelectorFamily(chainSelector)
if err != nil || family != chainsel.FamilyEVM {
return address
}

return strings.ToLower(address)
}

// AddressesEqual reports whether two addresses on the same chain refer to the same contract,
// using the case rules of that chain's family. Use it instead of == whenever an address that came
// from one source is compared against an address that came from another, since only one of them
// may be checksummed.
func AddressesEqual(chainSelector uint64, a, b string) bool {
return NormalizeAddress(chainSelector, a) == NormalizeAddress(chainSelector, b)
}
Loading
Loading