Skip to content

Commit

Permalink
[CCIP-2422] Streamlines Contract Version Checks (#1079)
Browse files Browse the repository at this point in the history
## Motivation

Version checks can get messy with lots of if statements or switch cases
when gating a test or other function that should only be able to work
with certain versions. Example:

```go
if offRampVersion, exists := TestCfg.VersionInput[contracts.OffRampContract]; exists {
	require.NotEqual(t, offRampVersion, contracts.V1_2_0, "Provided OffRamp contract version '%s' is not supported for this test", offRampVersion)
} else {
	require.FailNow(t, "OffRamp contract version not found in test config")
}
if onRampVersion, exists := TestCfg.VersionInput[contracts.OnRampContract]; exists {
	require.NotEqual(t, onRampVersion, contracts.V1_2_0, "Provided OnRamp contract version '%s' is not supported for this test", onRampVersion)
} else {
	require.FailNow(t, "OnRamp contract version not found in test config")
}
// And on and on for all versions
```

## Solution

Utilize a general contract version checking function. The new checks
look like this:

```go
err := contracts.HaveRequiredContractVersions(map[contracts.Name]contracts.Version{
	contracts.OffRampContract: contracts.V1_5_0_dev,
	contracts.OnRampContract:  contracts.V1_5_0_dev,
})
require.NoError(t, err, "Required contract versions not met")
```

This helps clean up [this PR
](#1030) immensely
  • Loading branch information
kalverra authored Jun 25, 2024
1 parent 3c849a0 commit 7ae908b
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 81 deletions.
32 changes: 32 additions & 0 deletions integration-tests/ccip-tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,38 @@ If you don't want to bother with any overrides, you can run with the default TOM
make test_smoke_ccip_default testname=TestSmokeCCIPForBidirectionalLane secret_toml="<the toml file with secrets string>"
```
```mermaid
---
title: Basic Docker Test Environment
---
flowchart
subgraph SD[DON]
CL1[Node 1]
CL2[Node 2]
CL3[Node 3]
CL4[Node 4]
CL5[Node 5]
CL6[Node 6]
CL1---CL2
CL2---CL3
CL3---CL4
CL4---CL5
CL5---CL6
end
subgraph Chains
SC1[[Private Chain 1]]
SC2[[Private Chain 2]]
end
SC1<-->SD
SC2<-->SD
MS([Mock Server])
MS-->SD
TC[/Test Code\]
TC<-->MS
TC<-->Chains
TC<-->SD
```
### Using Remote Kubernetes Cluster
For running more complex and intensive tests (like load and chaos tests) you need to connect the test to a Kubernetes cluster. These tests have more complex setup and running instructions. We endeavor to make these easier to run and configure, but for the time being please seek a member of the QA/Test Tooling team if you want to run these.
46 changes: 27 additions & 19 deletions integration-tests/ccip-tests/contracts/contract_deployer.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,25 @@ import (
"github.com/smartcontractkit/chainlink/v2/core/services/relay/evm"
)

// MatchContractVersionsOrAbove checks if the current contract versions for the test match or exceed the provided contract versions
func MatchContractVersionsOrAbove(requiredContractVersions map[Name]Version) error {
for contractName, r := range requiredContractVersions {
required := r
if contractVersion, ok := VersionMap[contractName]; !ok {
return fmt.Errorf("contract %s not found in version map", contractName)
} else if contractVersion.Compare(&required.Version) < 0 {
return fmt.Errorf("contract %s version %s is less than required version %s", contractName, contractVersion, required.Version)
}
}
return nil
}

// NeedTokenAdminRegistry checks if token admin registry is needed for the current version of ccip
// if the version is less than 1.5.0-dev, then token admin registry is not needed
func NeedTokenAdminRegistry() bool {
// find out the pool version
version := VersionMap[TokenPoolContract]
if version == Latest {
return true
}
currentSemver := semver.MustParse(string(version))
tokenAdminEnabledVersion := semver.MustParse("1.5.0-dev")
return currentSemver.Compare(tokenAdminEnabledVersion) >= 0
return MatchContractVersionsOrAbove(map[Name]Version{
TokenPoolContract: V1_5_0_dev,
}) == nil
}

// CCIPContractsDeployer provides the implementations for deploying CCIP ETH contracts
Expand Down Expand Up @@ -293,7 +301,7 @@ func (e *CCIPContractsDeployer) NewLockReleaseTokenPoolContract(addr common.Addr
error,
) {
version := VersionMap[TokenPoolContract]
e.logger.Info().Str("version", string(version)).Msg("New LockRelease Token Pool")
e.logger.Info().Str("Version", version.String()).Msg("New LockRelease Token Pool")
switch version {
case Latest:
pool, err := lock_release_token_pool.NewLockReleaseTokenPool(addr, wrappers.MustNewWrappedContractBackend(e.evmClient, nil))
Expand Down Expand Up @@ -362,7 +370,7 @@ func (e *CCIPContractsDeployer) NewUSDCTokenPoolContract(addr common.Address) (
error,
) {
version := VersionMap[TokenPoolContract]
e.logger.Info().Str("version", string(version)).Msg("New USDC Token Pool")
e.logger.Info().Str("Version", version.String()).Msg("New USDC Token Pool")
switch version {
case Latest:
pool, err := usdc_token_pool.NewUSDCTokenPool(addr, wrappers.MustNewWrappedContractBackend(e.evmClient, nil))
Expand Down Expand Up @@ -486,7 +494,7 @@ func (e *CCIPContractsDeployer) DeployLockReleaseTokenPoolContract(tokenAddr str
error,
) {
version := VersionMap[TokenPoolContract]
e.logger.Info().Str("version", string(version)).Msg("Deploying LockRelease Token Pool")
e.logger.Info().Str("Version", version.String()).Msg("Deploying LockRelease Token Pool")
token := common.HexToAddress(tokenAddr)
switch version {
case Latest:
Expand Down Expand Up @@ -568,7 +576,7 @@ func (e *CCIPContractsDeployer) NewCommitStore(addr common.Address) (
error,
) {
version := VersionMap[CommitStoreContract]
e.logger.Info().Str("version", string(version)).Msg("New CommitStore")
e.logger.Info().Str("Version", version.String()).Msg("New CommitStore")
switch version {
case Latest:
ins, err := commit_store.NewCommitStore(addr, wrappers.MustNewWrappedContractBackend(e.evmClient, nil))
Expand Down Expand Up @@ -612,7 +620,7 @@ func (e *CCIPContractsDeployer) DeployCommitStore(sourceChainSelector, destChain
if !ok {
return nil, fmt.Errorf("versioning not supported: %s", version)
}
e.logger.Info().Str("version", string(version)).Msg("Deploying CommitStore")
e.logger.Info().Str("Version", version.String()).Msg("Deploying CommitStore")
switch version {
case Latest:
address, _, instance, err := e.evmClient.DeployContract("CommitStore Contract", func(
Expand Down Expand Up @@ -762,7 +770,7 @@ func (e *CCIPContractsDeployer) NewPriceRegistry(addr common.Address) (
) {
var wrapper *PriceRegistryWrapper
version := VersionMap[PriceRegistryContract]
e.logger.Info().Str("version", string(version)).Msg("New PriceRegistry")
e.logger.Info().Str("Version", version.String()).Msg("New PriceRegistry")
switch version {
case Latest:
ins, err := price_registry.NewPriceRegistry(addr, wrappers.MustNewWrappedContractBackend(e.evmClient, nil))
Expand Down Expand Up @@ -803,7 +811,7 @@ func (e *CCIPContractsDeployer) DeployPriceRegistry(tokens []common.Address) (*P
var err error
var instance interface{}
version := VersionMap[PriceRegistryContract]
e.logger.Info().Str("version", string(version)).Msg("Deploying PriceRegistry")
e.logger.Info().Str("Version", version.String()).Msg("Deploying PriceRegistry")
switch version {
case Latest:
address, _, instance, err = e.evmClient.DeployContract("PriceRegistry", func(
Expand Down Expand Up @@ -885,7 +893,7 @@ func (e *CCIPContractsDeployer) NewOnRamp(addr common.Address) (
error,
) {
version := VersionMap[OnRampContract]
e.logger.Info().Str("version", string(version)).Msg("New OnRamp")
e.logger.Info().Str("Version", version.String()).Msg("New OnRamp")
e.logger.Info().
Str("Contract Address", addr.Hex()).
Str("Contract Name", "OnRamp").
Expand Down Expand Up @@ -933,7 +941,7 @@ func (e *CCIPContractsDeployer) DeployOnRamp(
linkTokenAddress common.Address,
) (*OnRamp, error) {
version := VersionMap[OnRampContract]
e.logger.Info().Str("version", string(version)).Msg("Deploying OnRamp")
e.logger.Info().Str("Version", version.String()).Msg("Deploying OnRamp")
switch version {
case V1_2_0:
feeTokenConfigV1_2_0 := make([]evm_2_evm_onramp_1_2_0.EVM2EVMOnRampFeeTokenConfigArgs, len(feeTokenConfig))
Expand Down Expand Up @@ -1069,7 +1077,7 @@ func (e *CCIPContractsDeployer) NewOffRamp(addr common.Address) (
error,
) {
version := VersionMap[OffRampContract]
e.logger.Info().Str("version", string(version)).Msg("New OffRamp")
e.logger.Info().Str("Version", version.String()).Msg("New OffRamp")
switch version {
case V1_2_0:
ins, err := evm_2_evm_offramp_1_2_0.NewEVM2EVMOffRamp(addr, wrappers.MustNewWrappedContractBackend(e.evmClient, nil))
Expand Down Expand Up @@ -1119,7 +1127,7 @@ func (e *CCIPContractsDeployer) DeployOffRamp(
tokenAdminRegistry common.Address,
) (*OffRamp, error) {
version := VersionMap[OffRampContract]
e.logger.Info().Str("version", string(version)).Msg("Deploying OffRamp")
e.logger.Info().Str("Version", version.String()).Msg("Deploying OffRamp")
switch version {
case V1_2_0:
address, _, instance, err := e.evmClient.DeployContract("OffRamp Contract", func(
Expand Down
102 changes: 75 additions & 27 deletions integration-tests/ccip-tests/contracts/contract_models.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import (
"fmt"
"math/big"
"strconv"
"strings"
"time"

"github.com/AlekSi/pointer"
"github.com/Masterminds/semver/v3"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
Expand Down Expand Up @@ -46,61 +48,107 @@ import (
"github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers"
)

var (
FiftyCoins = new(big.Int).Mul(big.NewInt(1e18), big.NewInt(50))
HundredCoins = new(big.Int).Mul(big.NewInt(1e18), big.NewInt(100))
)
// Name denotes a contract name
type Name string

// Version wraps a semver.Version object to provide some custom unmarshalling
type Version struct {
semver.Version
}

// MustVersion creates a new Version object from a semver string and panics if it fails
func MustVersion(version string) Version {
v := semver.MustParse(version)
return Version{Version: *v}
}

// UnmarshalTOML unmarshals TOML data into a Version object
func (v *Version) UnmarshalText(data []byte) error {
str := strings.Trim(string(data), `"`)
str = strings.Trim(str, `'`)
if strings.ToLower(str) == "latest" {
*v = Latest
return nil
}
ver, err := semver.NewVersion(str)
if err != nil {
return fmt.Errorf("failed to parse version from '%s': %w", str, err)
}
v.Version = *ver
return nil
}

type ContractVersion string
// Latest returns true if the version is the latest version
func (v *Version) Latest() bool {
return v.Version.Equal(&Latest.Version)
}

const (
Network = "Network Name"
V1_2_0 ContractVersion = "1.2.0"
V1_4_0 ContractVersion = "1.4.0"
LatestPoolVersion ContractVersion = "1.5.0-dev"
Latest ContractVersion = "latest"
PriceRegistryContract = "PriceRegistry"
OffRampContract = "OffRamp"
OnRampContract = "OnRamp"
TokenPoolContract = "TokenPool"
CommitStoreContract = "CommitStore"
Network = "Network Name"
PriceRegistryContract Name = "PriceRegistry"
OffRampContract Name = "OffRamp"
OnRampContract Name = "OnRamp"
TokenPoolContract Name = "TokenPool"
CommitStoreContract Name = "CommitStore"

defaultDestByteOverhead = uint32(32)
defaultDestGasOverhead = uint32(29_000)
)

var (
VersionMap = map[string]ContractVersion{
V1_2_0 = MustVersion("1.2.0")
V1_4_0 = MustVersion("1.4.0")
V1_5_0_dev = MustVersion("1.5.0-dev")
LatestPoolVersion = V1_5_0_dev
Latest = V1_5_0_dev
VersionMap = map[Name]Version{
PriceRegistryContract: Latest,
OffRampContract: Latest,
OnRampContract: Latest,
CommitStoreContract: Latest,
TokenPoolContract: Latest,
}
SupportedContracts = map[string]map[ContractVersion]bool{
SupportedContracts = map[Name]map[string]bool{
PriceRegistryContract: {
Latest: true,
V1_2_0: true,
Latest.String(): true,
V1_2_0.String(): true,
},
OffRampContract: {
Latest: true,
V1_2_0: true,
Latest.String(): true,
V1_2_0.String(): true,
},
OnRampContract: {
Latest: true,
V1_2_0: true,
Latest.String(): true,
V1_2_0.String(): true,
},
CommitStoreContract: {
Latest: true,
V1_2_0: true,
Latest.String(): true,
V1_2_0.String(): true,
},
TokenPoolContract: {
Latest: true,
V1_4_0: true,
Latest.String(): true,
V1_4_0.String(): true,
},
}

FiftyCoins = new(big.Int).Mul(big.NewInt(1e18), big.NewInt(50))
HundredCoins = new(big.Int).Mul(big.NewInt(1e18), big.NewInt(100))
)

// CheckVersionSupported checks if a given version is supported for a given contract
func CheckVersionSupported(name Name, version Version) error {
if contract, ok := SupportedContracts[name]; ok {
if isSupported, ok := contract[version.String()]; ok {
if isSupported {
return nil
}
return fmt.Errorf("version %s is not supported for contract %s", version.String(), name)
}
return fmt.Errorf("version %s is not supported for contract %s", version.String(), name)
}
return fmt.Errorf("contract %s is not supported", name)
}

type RateLimiterConfig struct {
IsEnabled bool
Rate *big.Int
Expand Down
24 changes: 9 additions & 15 deletions integration-tests/ccip-tests/smoke/ccip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,16 +408,11 @@ func TestSmokeCCIPSelfServeRateLimitOnRamp(t *testing.T) {

log := logging.GetTestLogger(t)
TestCfg := testsetups.NewCCIPTestConfig(t, log, testconfig.Smoke)
if offRampVersion, exists := TestCfg.VersionInput[contracts.OffRampContract]; exists {
require.NotEqual(t, offRampVersion, contracts.V1_2_0, "Provided OffRamp contract version '%s' is not supported for this test", offRampVersion)
} else {
require.FailNow(t, "OffRamp contract version not found in test config")
}
if onRampVersion, exists := TestCfg.VersionInput[contracts.OnRampContract]; exists {
require.NotEqual(t, onRampVersion, contracts.V1_2_0, "Provided OnRamp contract version '%s' is not supported for this test", onRampVersion)
} else {
require.FailNow(t, "OnRamp contract version not found in test config")
}
err := contracts.MatchContractVersionsOrAbove(map[contracts.Name]contracts.Version{
contracts.OffRampContract: contracts.V1_5_0_dev,
contracts.OnRampContract: contracts.V1_5_0_dev,
})
require.NoError(t, err, "Required contract versions not met")

setUpOutput := testsetups.CCIPDefaultTestSetUp(t, &log, "smoke-ccip", nil, TestCfg)
if len(setUpOutput.Lanes) == 0 {
Expand Down Expand Up @@ -539,11 +534,10 @@ func TestSmokeCCIPSelfServeRateLimitOffRamp(t *testing.T) {

log := logging.GetTestLogger(t)
TestCfg := testsetups.NewCCIPTestConfig(t, log, testconfig.Smoke)
if offRampVersion, exists := TestCfg.VersionInput[contracts.OffRampContract]; exists {
require.NotEqual(t, offRampVersion, contracts.V1_2_0, "Provided OffRamp contract version '%s' is not supported for this test", offRampVersion)
} else {
require.FailNow(t, "OffRamp contract version not found in test config")
}
err := contracts.MatchContractVersionsOrAbove(map[contracts.Name]contracts.Version{
contracts.OffRampContract: contracts.V1_5_0_dev,
})
require.NoError(t, err, "Required contract versions not met")
require.True(t, TestCfg.SelectedNetworks[0].Simulated, "This test relies on timing assumptions and should only be run on simulated networks")

// Set the default permissionless exec threshold lower so that we can manually execute the transactions faster
Expand Down
8 changes: 4 additions & 4 deletions integration-tests/ccip-tests/testconfig/ccip.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,10 +375,10 @@ func (c *CCIPContractConfig) ContractsData() ([]byte, error) {
}

type CCIP struct {
Env *Common `toml:",omitempty"`
ContractVersions map[string]*ccipcontracts.ContractVersion `toml:",omitempty"`
Deployments *CCIPContractConfig `toml:",omitempty"`
Groups map[string]*CCIPTestGroupConfig `toml:",omitempty"`
Env *Common `toml:",omitempty"`
ContractVersions map[ccipcontracts.Name]ccipcontracts.Version `toml:",omitempty"`
Deployments *CCIPContractConfig `toml:",omitempty"`
Groups map[string]*CCIPTestGroupConfig `toml:",omitempty"`
}

func (c *CCIP) Validate() error {
Expand Down
Loading

0 comments on commit 7ae908b

Please sign in to comment.