Skip to content

Commit

Permalink
Ccip-3076 deployment package (#14209)
Browse files Browse the repository at this point in the history
* deployment package inclusion

* go mod

* Add README

* Add changeset

* Fix changeset

* change confirm function

* ignore linter for now

* core generate

* add todo ticket

* fix test

---------

Co-authored-by: Lukasz <[email protected]>
Co-authored-by: connorwstein <[email protected]>
  • Loading branch information
3 people authored and asoliman92 committed Aug 27, 2024
1 parent b136555 commit 6298da1
Show file tree
Hide file tree
Showing 16 changed files with 289 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"math/big"
"net/http"
"net/http/httptest"
"slices"
"strconv"
"strings"
"testing"
Expand Down Expand Up @@ -36,6 +37,7 @@ import (
"github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox"

cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip"
coretypes "github.com/smartcontractkit/chainlink-common/pkg/types/core/mocks"

"github.com/smartcontractkit/chainlink/v2/core/chains/evm/client"
v2 "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml"
Expand All @@ -47,6 +49,7 @@ import (
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/commit_store"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_offramp"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/price_registry_1_2_0"
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils"
"github.com/smartcontractkit/chainlink/v2/core/logger"
"github.com/smartcontractkit/chainlink/v2/core/logger/audit"
Expand Down Expand Up @@ -378,7 +381,6 @@ func setupNodeCCIP(
fmt.Sprintf("127.0.0.1:%d", port),
}
c.Log.Level = &loglevel
c.Feature.CCIP = &trueRef
c.Feature.UICSAKeys = &trueRef
c.Feature.FeedsManager = &trueRef
c.OCR.Enabled = &falseRef
Expand Down Expand Up @@ -458,9 +460,10 @@ func setupNodeCCIP(
}
loopRegistry := plugins.NewLoopRegistry(lggr.Named("LoopRegistry"), config.Tracing())
relayerFactory := chainlink.RelayerFactory{
Logger: lggr,
LoopRegistry: loopRegistry,
GRPCOpts: loop.GRPCOpts{},
Logger: lggr,
LoopRegistry: loopRegistry,
GRPCOpts: loop.GRPCOpts{},
CapabilitiesRegistry: coretypes.NewCapabilitiesRegistry(t),
}
testCtx := testutils.Context(t)
// evm alway enabled for backward compatibility
Expand Down Expand Up @@ -761,6 +764,47 @@ func (c *CCIPIntegrationTestHarness) NoNodesHaveExecutedSeqNum(t *testing.T, seq
return log
}

func (c *CCIPIntegrationTestHarness) EventuallyPriceRegistryUpdated(t *testing.T, block uint64, srcSelector uint64, tokens []common.Address, sourceNative common.Address, priceRegistryOpts ...common.Address) {
var priceRegistry *price_registry_1_2_0.PriceRegistry
var err error
if len(priceRegistryOpts) > 0 {
priceRegistry, err = price_registry_1_2_0.NewPriceRegistry(priceRegistryOpts[0], c.Dest.Chain)
require.NoError(t, err)
} else {
require.NotNil(t, c.Dest.PriceRegistry, "no priceRegistry configured")
priceRegistry = c.Dest.PriceRegistry
}

g := gomega.NewGomegaWithT(t)
g.Eventually(func() bool {
it, err := priceRegistry.FilterUsdPerTokenUpdated(&bind.FilterOpts{Start: block}, tokens)
g.Expect(err).NotTo(gomega.HaveOccurred(), "Error filtering UsdPerTokenUpdated event")

tokensFetched := make([]common.Address, 0, len(tokens))
for it.Next() {
tokenFetched := it.Event.Token
tokensFetched = append(tokensFetched, tokenFetched)
t.Log("Token price updated", tokenFetched.String(), it.Event.Value.String(), it.Event.Timestamp.String())
}

for _, token := range tokens {
if !slices.Contains(tokensFetched, token) {
return false
}
}

return true
}, testutils.WaitTimeout(t), 10*time.Second).Should(gomega.BeTrue(), "Tokens prices has not been updated")

g.Eventually(func() bool {
it, err := priceRegistry.FilterUsdPerUnitGasUpdated(&bind.FilterOpts{Start: block}, []uint64{srcSelector})
g.Expect(err).NotTo(gomega.HaveOccurred(), "Error filtering UsdPerUnitGasUpdated event")
g.Expect(it.Next()).To(gomega.BeTrue(), "No UsdPerUnitGasUpdated event found")

return true
}, testutils.WaitTimeout(t), 10*time.Second).Should(gomega.BeTrue(), "source gas price has not been updated")
}

func (c *CCIPIntegrationTestHarness) EventuallyCommitReportAccepted(t *testing.T, currentBlock uint64, commitStoreOpts ...common.Address) commit_store.CommitStoreCommitReport {
var commitStore *commit_store.CommitStore
var err error
Expand Down
118 changes: 107 additions & 11 deletions integration-tests/deployment/address_book.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,112 @@
package deployment

import "fmt"
import (
"fmt"
"strings"

"github.com/Masterminds/semver/v3"
"github.com/ethereum/go-ethereum/common"
"github.com/pkg/errors"
chainsel "github.com/smartcontractkit/chain-selectors"
)

var (
ErrInvalidChainSelector = fmt.Errorf("invalid chain selector")
ErrInvalidAddress = fmt.Errorf("invalid address")
)

// ContractType is a simple string type for identifying contract types.
type ContractType string

var (
Version1_0_0 = *semver.MustParse("1.0.0")
Version1_1_0 = *semver.MustParse("1.1.0")
Version1_2_0 = *semver.MustParse("1.2.0")
Version1_5_0 = *semver.MustParse("1.5.0")
Version1_6_0_dev = *semver.MustParse("1.6.0-dev")
)

type TypeAndVersion struct {
Type ContractType
Version semver.Version
}

func (tv TypeAndVersion) String() string {
return fmt.Sprintf("%s %s", tv.Type, tv.Version.String())
}

func (tv TypeAndVersion) Equal(other TypeAndVersion) bool {
return tv.String() == other.String()
}

func MustTypeAndVersionFromString(s string) TypeAndVersion {
tv, err := TypeAndVersionFromString(s)
if err != nil {
panic(err)
}
return tv
}

// Note this will become useful for validation. When we want
// to assert an onchain call to typeAndVersion yields whats expected.
func TypeAndVersionFromString(s string) (TypeAndVersion, error) {
parts := strings.Split(s, " ")
if len(parts) != 2 {
return TypeAndVersion{}, fmt.Errorf("invalid type and version string: %s", s)
}
v, err := semver.NewVersion(parts[1])
if err != nil {
return TypeAndVersion{}, err
}
return TypeAndVersion{
Type: ContractType(parts[0]),
Version: *v,
}, nil
}

func NewTypeAndVersion(t ContractType, v semver.Version) TypeAndVersion {
return TypeAndVersion{
Type: t,
Version: v,
}
}

// AddressBook is a simple interface for storing and retrieving contract addresses across
// chains. It is family agnostic.
// chains. It is family agnostic as the keys are chain selectors.
// We store rather than derive typeAndVersion as some contracts do not support it.
// For ethereum addresses are always stored in EIP55 format.
type AddressBook interface {
Save(chainSelector uint64, address string, typeAndVersion string) error
Addresses() (map[uint64]map[string]string, error)
AddressesForChain(chain uint64) (map[string]string, error)
Save(chainSelector uint64, address string, tv TypeAndVersion) error
Addresses() (map[uint64]map[string]TypeAndVersion, error)
AddressesForChain(chain uint64) (map[string]TypeAndVersion, error)
// Allows for merging address books (e.g. new deployments with existing ones)
Merge(other AddressBook) error
}

type AddressBookMap struct {
AddressesByChain map[uint64]map[string]string
AddressesByChain map[uint64]map[string]TypeAndVersion
}

func (m *AddressBookMap) Save(chainSelector uint64, address string, typeAndVersion string) error {
func (m *AddressBookMap) Save(chainSelector uint64, address string, typeAndVersion TypeAndVersion) error {
_, exists := chainsel.ChainBySelector(chainSelector)
if !exists {
return errors.Wrapf(ErrInvalidChainSelector, "chain selector %d not found", chainSelector)
}
if address == "" || address == common.HexToAddress("0x0").Hex() {
return errors.Wrap(ErrInvalidAddress, "address cannot be empty")
}
if common.IsHexAddress(address) {
// IMPORTANT: WE ALWAYS STANDARDIZE ETHEREUM ADDRESS STRINGS TO EIP55
address = common.HexToAddress(address).Hex()
} else {
return errors.Wrapf(ErrInvalidAddress, "address %s is not a valid Ethereum address, only Ethereum addresses supported", address)
}
if typeAndVersion.Type == "" {
return fmt.Errorf("type cannot be empty")
}
if _, exists := m.AddressesByChain[chainSelector]; !exists {
// First time chain add, create map
m.AddressesByChain[chainSelector] = make(map[string]string)
m.AddressesByChain[chainSelector] = make(map[string]TypeAndVersion)
}
if _, exists := m.AddressesByChain[chainSelector][address]; exists {
return fmt.Errorf("address %s already exists for chain %d", address, chainSelector)
Expand All @@ -28,11 +115,11 @@ func (m *AddressBookMap) Save(chainSelector uint64, address string, typeAndVersi
return nil
}

func (m *AddressBookMap) Addresses() (map[uint64]map[string]string, error) {
func (m *AddressBookMap) Addresses() (map[uint64]map[string]TypeAndVersion, error) {
return m.AddressesByChain, nil
}

func (m *AddressBookMap) AddressesForChain(chain uint64) (map[string]string, error) {
func (m *AddressBookMap) AddressesForChain(chain uint64) (map[string]TypeAndVersion, error) {
if _, exists := m.AddressesByChain[chain]; !exists {
return nil, fmt.Errorf("chain %d not found", chain)
}
Expand All @@ -55,8 +142,17 @@ func (m *AddressBookMap) Merge(ab AddressBook) error {
return nil
}

// TODO: Maybe could add an environment argument
// which would ensure only mainnet/testnet chain selectors are used
// for further safety?
func NewMemoryAddressBook() *AddressBookMap {
return &AddressBookMap{
AddressesByChain: make(map[uint64]map[string]string),
AddressesByChain: make(map[uint64]map[string]TypeAndVersion),
}
}

func NewMemoryAddressBookFromMap(addressesByChain map[uint64]map[string]TypeAndVersion) *AddressBookMap {
return &AddressBookMap{
AddressesByChain: addressesByChain,
}
}
117 changes: 79 additions & 38 deletions integration-tests/deployment/address_book_test.go
Original file line number Diff line number Diff line change
@@ -1,71 +1,112 @@
package deployment

import (
"errors"
"testing"

"github.com/ethereum/go-ethereum/common"
chainsel "github.com/smartcontractkit/chain-selectors"
"github.com/stretchr/testify/require"
"gotest.tools/v3/assert"
)

func TestAddressBook(t *testing.T) {
func TestAddressBook_Save(t *testing.T) {
ab := NewMemoryAddressBook()
err := ab.Save(1, "0x1", "OnRamp 1.0.0")
onRamp100 := NewTypeAndVersion("OnRamp", Version1_0_0)
onRamp110 := NewTypeAndVersion("OnRamp", Version1_1_0)
addr1 := common.HexToAddress("0x1").String()
addr2 := common.HexToAddress("0x2").String()

err := ab.Save(chainsel.TEST_90000001.Selector, addr1, onRamp100)
require.NoError(t, err)
// Duplicate address will error
err = ab.Save(1, "0x1", "OnRamp 1.0.0")

// Check input validation
err = ab.Save(chainsel.TEST_90000001.Selector, "asdlfkj", onRamp100)
require.Error(t, err)
assert.Equal(t, errors.Is(err, ErrInvalidAddress), true, "err %s", err)
err = ab.Save(0, addr1, onRamp100)
require.Error(t, err)
assert.Equal(t, errors.Is(err, ErrInvalidChainSelector), true)
// Duplicate
err = ab.Save(chainsel.TEST_90000001.Selector, addr1, onRamp100)
require.Error(t, err)
// Zero address
err = ab.Save(chainsel.TEST_90000001.Selector, common.HexToAddress("0x0").Hex(), onRamp100)
require.Error(t, err)

// Distinct address same TV will not
err = ab.Save(1, "0x2", "OnRamp 1.0.0")
err = ab.Save(chainsel.TEST_90000001.Selector, addr2, onRamp100)
require.NoError(t, err)
// Same address different chain will not error
err = ab.Save(2, "0x1", "OnRamp 1.0.0")
err = ab.Save(chainsel.TEST_90000002.Selector, addr1, onRamp100)
require.NoError(t, err)
// We can save different versions of the same contract
err = ab.Save(2, "0x2", "OnRamp 1.2.0")
err = ab.Save(chainsel.TEST_90000002.Selector, addr2, onRamp110)
require.NoError(t, err)

addresses, err := ab.Addresses()
require.NoError(t, err)
assert.DeepEqual(t, addresses, map[uint64]map[string]string{
1: {
"0x1": "OnRamp 1.0.0",
"0x2": "OnRamp 1.0.0",
assert.DeepEqual(t, addresses, map[uint64]map[string]TypeAndVersion{
chainsel.TEST_90000001.Selector: {
addr1: onRamp100,
addr2: onRamp100,
},
2: {
"0x1": "OnRamp 1.0.0",
"0x2": "OnRamp 1.2.0",
chainsel.TEST_90000002.Selector: {
addr1: onRamp100,
addr2: onRamp110,
},
})
}

// Test merge
ab2 := NewMemoryAddressBook()
require.NoError(t, ab2.Save(3, "0x3", "OnRamp 1.0.0"))
require.NoError(t, ab.Merge(ab2))
// Other address book should remain unchanged.
addresses, err = ab2.Addresses()
require.NoError(t, err)
assert.DeepEqual(t, addresses, map[uint64]map[string]string{
3: {
"0x3": "OnRamp 1.0.0",
func TestAddressBook_Merge(t *testing.T) {
onRamp100 := NewTypeAndVersion("OnRamp", Version1_0_0)
onRamp110 := NewTypeAndVersion("OnRamp", Version1_1_0)
addr1 := common.HexToAddress("0x1").String()
addr2 := common.HexToAddress("0x2").String()
a1 := NewMemoryAddressBookFromMap(map[uint64]map[string]TypeAndVersion{
chainsel.TEST_90000001.Selector: {
addr1: onRamp100,
},
})
// Existing addressbook should contain the new elements.
addresses, err = ab.Addresses()
require.NoError(t, err)
assert.DeepEqual(t, addresses, map[uint64]map[string]string{
1: {
"0x1": "OnRamp 1.0.0",
"0x2": "OnRamp 1.0.0",
a2 := NewMemoryAddressBookFromMap(map[uint64]map[string]TypeAndVersion{
chainsel.TEST_90000001.Selector: {
addr2: onRamp100,
},
2: {
"0x1": "OnRamp 1.0.0",
"0x2": "OnRamp 1.2.0",
chainsel.TEST_90000002.Selector: {
addr1: onRamp110,
},
3: {
"0x3": "OnRamp 1.0.0",
})
require.NoError(t, a1.Merge(a2))

addresses, err := a1.Addresses()
require.NoError(t, err)
assert.DeepEqual(t, addresses, map[uint64]map[string]TypeAndVersion{
chainsel.TEST_90000001.Selector: {
addr1: onRamp100,
addr2: onRamp100,
},
chainsel.TEST_90000002.Selector: {
addr1: onRamp110,
},
})

// Merge to an existing chain.
require.NoError(t, ab2.Save(2, "0x3", "OffRamp 1.0.0"))
// Merge with conflicting addresses should error
a3 := NewMemoryAddressBookFromMap(map[uint64]map[string]TypeAndVersion{
chainsel.TEST_90000001.Selector: {
addr1: onRamp100,
},
})
require.Error(t, a1.Merge(a3))
// a1 should not have changed
addresses, err = a1.Addresses()
require.NoError(t, err)
assert.DeepEqual(t, addresses, map[uint64]map[string]TypeAndVersion{
chainsel.TEST_90000001.Selector: {
addr1: onRamp100,
addr2: onRamp100,
},
chainsel.TEST_90000002.Selector: {
addr1: onRamp110,
},
})
}
Loading

0 comments on commit 6298da1

Please sign in to comment.