Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ChainComponents EVM Interface Tests #13735

Merged
merged 1 commit into from
Sep 5, 2024
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
5 changes: 5 additions & 0 deletions .changeset/loud-months-act.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

#internal Added ChainWriter to ChainReader tests
2 changes: 1 addition & 1 deletion core/scripts/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ require (
github.com/prometheus/client_golang v1.17.0
github.com/shopspring/decimal v1.4.0
github.com/smartcontractkit/chainlink-automation v1.0.4
github.com/smartcontractkit/chainlink-common v0.2.2-0.20240903184200-6488292a85e3
github.com/smartcontractkit/chainlink-common v0.2.2-0.20240905145927-2ff0f9628f4d
github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000
github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7
github.com/spf13/cobra v1.8.0
Expand Down
4 changes: 2 additions & 2 deletions core/scripts/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1190,8 +1190,8 @@ github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8um
github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM=
github.com/smartcontractkit/chainlink-ccip v0.0.0-20240905130411-ebd9328c9870 h1:/NYLKZOQhOAPFszrq86gPBD47Rt6TF69GnZ8Vf7qdVQ=
github.com/smartcontractkit/chainlink-ccip v0.0.0-20240905130411-ebd9328c9870/go.mod h1:Z9lQ5t20kRk28pzRLnqAJZUVOw8E6/siA3P3MLyKqoM=
github.com/smartcontractkit/chainlink-common v0.2.2-0.20240903184200-6488292a85e3 h1:fkfOoAPviqO2rN8ngvejsDa7WKcw4paGEFA4/Znu0L0=
github.com/smartcontractkit/chainlink-common v0.2.2-0.20240903184200-6488292a85e3/go.mod h1:D/qaCoq0SxXzg5NRN5FtBRv98VBf+D2NOC++RbvvuOc=
github.com/smartcontractkit/chainlink-common v0.2.2-0.20240905145927-2ff0f9628f4d h1:VVtgseTBEJN0/NcewMcka1qwslKhY1HPXs4EEpZa7ek=
github.com/smartcontractkit/chainlink-common v0.2.2-0.20240905145927-2ff0f9628f4d/go.mod h1:D/qaCoq0SxXzg5NRN5FtBRv98VBf+D2NOC++RbvvuOc=
github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 h1:NBQLtqk8zsyY4qTJs+NElI3aDFTcAo83JHvqD04EvB0=
github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45/go.mod h1:LV0h7QBQUpoC2UUi6TcUvcIFm1xjP/DtEcqV8+qeLUs=
github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240904093355-e40169857652 h1:0aZ3HiEz2bMM5ywHAyKlFMN95qTzpNDn7uvnHLrFX6s=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package evm_test

import (
"context"
"crypto/ecdsa"
"fmt"
"math"
"math/big"
Expand All @@ -18,26 +19,33 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config"
clcommontypes "github.com/smartcontractkit/chainlink-common/pkg/types"
. "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" //nolint common practice to import test mods with .
"github.com/smartcontractkit/chainlink/v2/core/internal/cltest"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey"

"github.com/smartcontractkit/chainlink/v2/core/logger"
"github.com/smartcontractkit/chainlink/v2/core/services/chainlink"
"github.com/smartcontractkit/chainlink/v2/core/services/relay/evm"
"github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types"

commontestutils "github.com/smartcontractkit/chainlink-common/pkg/loop/testutils"

"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/client"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller"
evmtxmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr"
"github.com/smartcontractkit/chainlink/v2/core/internal/cltest"
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils"
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest"
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest"
keytypes "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey"
. "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/evmtesting" //nolint common practice to import test mods with .
)

const commonGasLimitOnEvms = uint64(4712388)

func TestChainReaderEventsInitValidation(t *testing.T) {
func TestContractReaderEventsInitValidation(t *testing.T) {
tests := []struct {
name string
chainContractReaders map[string]types.ChainContractReader
Expand Down Expand Up @@ -143,43 +151,77 @@ func TestChainReaderEventsInitValidation(t *testing.T) {
}
}

func TestChainReader(t *testing.T) {
func TestChainComponents(t *testing.T) {
t.Parallel()
it := &EVMChainReaderInterfaceTester[*testing.T]{Helper: &helper{}}
it := &EVMChainComponentsInterfaceTester[*testing.T]{Helper: &helper{}}

it.Helper.Init(t)

// add new subtests here so that it can be run on real chains too
RunChainReaderEvmTests(t, it)
RunChainReaderInterfaceTests[*testing.T](t, commontestutils.WrapChainReaderTesterForLoop(it))
RunChainComponentsEvmTests(t, it)
RunContractReaderInterfaceTests[*testing.T](t, commontestutils.WrapContractReaderTesterForLoop(it), false)
}

type helper struct {
sim *backends.SimulatedBackend
auth *bind.TransactOpts
sim *backends.SimulatedBackend
accounts []*bind.TransactOpts
deployerKey *ecdsa.PrivateKey
senderKey *ecdsa.PrivateKey
txm evmtxmgr.TxManager
client client.Client
db *sqlx.DB
}

func (h *helper) MustGenerateRandomKey(t *testing.T) ethkey.KeyV2 {
return cltest.MustGenerateRandomKey(t)
func (h *helper) Init(t *testing.T) {
h.SetupKeys(t)

h.accounts = h.Accounts(t)

h.db = pgtest.NewSqlxDB(t)

h.Backend()
h.client = h.Client(t)

h.txm = h.TXM(t, h.client)
h.Commit()
}

func (h *helper) GasPriceBufferPercent() int64 {
return 0
func (h *helper) SetupKeys(t *testing.T) {
deployerPkey, err := crypto.GenerateKey()
require.NoError(t, err)
h.deployerKey = deployerPkey

senderPkey, err := crypto.GenerateKey()
require.NoError(t, err)
h.senderKey = senderPkey
}

func (h *helper) SetupAuth(t *testing.T) *bind.TransactOpts {
privateKey, err := crypto.GenerateKey()
func (h *helper) Accounts(t *testing.T) []*bind.TransactOpts {
if h.accounts != nil {
return h.accounts
}
deployer, err := bind.NewKeyedTransactorWithChainID(h.deployerKey, big.NewInt(1337))
require.NoError(t, err)

h.auth, err = bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(1337))
sender, err := bind.NewKeyedTransactorWithChainID(h.senderKey, big.NewInt(1337))
require.NoError(t, err)

h.Backend()
h.Commit()
return h.auth
return []*bind.TransactOpts{deployer, sender}
}

func (h *helper) MustGenerateRandomKey(t *testing.T) ethkey.KeyV2 {
return cltest.MustGenerateRandomKey(t)
}

func (h *helper) GasPriceBufferPercent() int64 {
return 0
}

func (h *helper) Backend() bind.ContractBackend {
if h.sim == nil {
h.sim = backends.NewSimulatedBackend(
core.GenesisAlloc{h.auth.From: {Balance: big.NewInt(math.MaxInt64)}}, commonGasLimitOnEvms*5000)
core.GenesisAlloc{h.accounts[0].From: {Balance: big.NewInt(math.MaxInt64)}, h.accounts[1].From: {Balance: big.NewInt(math.MaxInt64)}}, commonGasLimitOnEvms*5000)
cltest.Mine(h.sim, 1*time.Second)
}

return h.sim
Expand All @@ -190,6 +232,9 @@ func (h *helper) Commit() {
}

func (h *helper) Client(t *testing.T) client.Client {
if h.client != nil {
return h.client
}
return client.NewSimulatedBackendClient(t, h.sim, big.NewInt(1337))
}

Expand All @@ -205,6 +250,18 @@ func (h *helper) Context(t *testing.T) context.Context {
return testutils.Context(t)
}

func (h *helper) ChainReaderEVMClient(ctx context.Context, t *testing.T, ht logpoller.HeadTracker, conf types.ChainReaderConfig) client.Client {
// wrap the client so that we can mock historical contract state
cwh := &evm.ClientWithContractHistory{Client: h.Client(t), HT: ht}
require.NoError(t, cwh.Init(ctx, conf))
return cwh
}

func (h *helper) WrappedChainWriter(cw clcommontypes.ChainWriter, client client.Client) clcommontypes.ChainWriter {
cwhw := evm.NewChainWriterHistoricalWrapper(cw, client.(*evm.ClientWithContractHistory))
return cwhw
}

func (h *helper) MaxWaitTimeForEvents() time.Duration {
// From trial and error, when running on CI, sometimes the boxes get slow
maxWaitTime := time.Second * 30
Expand All @@ -216,6 +273,41 @@ func (h *helper) MaxWaitTimeForEvents() time.Duration {
}
maxWaitTime = time.Second * time.Duration(waitS)
}

return maxWaitTime
}

func (h *helper) TXM(t *testing.T, client client.Client) evmtxmgr.TxManager {
if h.txm != nil {
return h.txm
}
db := h.db

clconfig := configtest.NewGeneralConfigSimulated(t, func(c *chainlink.Config, s *chainlink.Secrets) {
c.Database.Listener.FallbackPollInterval = commonconfig.MustNewDuration(100 * time.Millisecond)
c.EVM[0].GasEstimator.EIP1559DynamicFees = ptr(true)
})

clconfig.EVMConfigs()[0].GasEstimator.PriceMax = assets.GWei(100)

app := cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, clconfig, h.sim, db, client)
err := app.Start(h.Context(t))
Comment on lines +292 to +293
Copy link
Contributor

Choose a reason for hiding this comment

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

seems overkill to start a whole app just for keystore and chain.txm, is there simpler way to do this? I am pretty sure that you can create a keystore easily

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, keystore can be created easier as I do in the chainlink-evm-testing implementation, however, I'm not sure if there's an easier way to start up a TXM without the underlying simulated app / chain, but I can look into this.

Copy link
Contributor

@patrick-dowell patrick-dowell Sep 5, 2024

Choose a reason for hiding this comment

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

Yes, keystore can be created easier as I do in the chainlink-evm-testing implementation, however, I'm not sure if there's an easier way to start up a TXM without the underlying simulated app / chain, but I can look into this.

Has this been resolved?

Copy link
Contributor

@ilija42 ilija42 Sep 5, 2024

Choose a reason for hiding this comment

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

I see txm being initialized on its own in tests in three places 1, 2 , 3, can we reuse any of these approaches so that we don't have to init the whole app?

Copy link
Contributor Author

@silaslenihan silaslenihan Sep 5, 2024

Choose a reason for hiding this comment

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

The issue with these examples is they don't implement the underlying components of the TXM (i.e. Broadcaster, confirmer, finalizer, etc.) that are necessary to actually send transactions (they just leave them nil). There may be a way to individually set up these underlying components, but I am not sure it is worth the effort

require.NoError(t, err)

keyStore := app.KeyStore.Eth()

keyStore.XXXTestingOnlyAdd(h.Context(t), keytypes.FromPrivateKey(h.deployerKey))
require.NoError(t, keyStore.Add(h.Context(t), h.accounts[0].From, h.ChainID()))
require.NoError(t, keyStore.Enable(h.Context(t), h.accounts[0].From, h.ChainID()))

keyStore.XXXTestingOnlyAdd(h.Context(t), keytypes.FromPrivateKey(h.senderKey))
require.NoError(t, keyStore.Add(h.Context(t), h.accounts[1].From, h.ChainID()))
require.NoError(t, keyStore.Enable(h.Context(t), h.accounts[1].From, h.ChainID()))

chain, err := app.GetRelayers().LegacyEVMChains().Get((h.ChainID()).String())
require.NoError(t, err)

h.txm = chain.TxManager()
return h.txm
}

func ptr[T any](v T) *T { return &v }
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package evmtesting
package evm

import (
"context"
Expand All @@ -14,9 +14,9 @@ import (

clcommontypes "github.com/smartcontractkit/chainlink-common/pkg/types"
. "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" //nolint common practice to import test mods with .

"github.com/smartcontractkit/chainlink/v2/core/chains/evm/client"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller"
"github.com/smartcontractkit/chainlink/v2/core/services/relay/evm"
"github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types"
)

Expand All @@ -30,7 +30,7 @@ type ClientWithContractHistory struct {

func (cwh *ClientWithContractHistory) Init(_ context.Context, config types.ChainReaderConfig) error {
cwh.valsWithCall = make(map[int64]valWithCall)
parsedTypes := evm.ParsedTypes{
parsedTypes := ParsedTypes{
EncoderDefs: make(map[string]types.CodecEntry),
DecoderDefs: make(map[string]types.CodecEntry),
}
Expand All @@ -47,12 +47,12 @@ func (cwh *ClientWithContractHistory) Init(_ context.Context, config types.Chain
continue
}

inputMod, err := readDef.InputModifications.ToModifier(evm.DecoderHooks...)
inputMod, err := readDef.InputModifications.ToModifier(DecoderHooks...)
if err != nil {
return err
}

outputMod, err := readDef.OutputModifications.ToModifier(evm.DecoderHooks...)
outputMod, err := readDef.OutputModifications.ToModifier(DecoderHooks...)
if err != nil {
return err
}
Expand All @@ -67,8 +67,8 @@ func (cwh *ClientWithContractHistory) Init(_ context.Context, config types.Chain
return err
}

parsedTypes.EncoderDefs[evm.WrapItemType(contractName, genericName, true)] = input
parsedTypes.DecoderDefs[evm.WrapItemType(contractName, genericName, false)] = output
parsedTypes.EncoderDefs[WrapItemType(contractName, genericName, true)] = input
parsedTypes.DecoderDefs[WrapItemType(contractName, genericName, false)] = output
}
}

Expand All @@ -90,7 +90,6 @@ func (cwh *ClientWithContractHistory) SetUintLatestValue(ctx context.Context, va
ExpectedGetLatestValueArgs: forCall,
val: val,
}

return nil
}

Expand Down Expand Up @@ -123,7 +122,7 @@ func (cwh *ClientWithContractHistory) CallContract(ctx context.Context, msg ethe
}

// encode the expected call to compare with the actual call
dataToCmp, err := cwh.codec.Encode(ctx, valAndCall.Params, evm.WrapItemType(valAndCall.ContractName, valAndCall.ReadName, true))
dataToCmp, err := cwh.codec.Encode(ctx, valAndCall.Params, WrapItemType(valAndCall.ContractName, valAndCall.ReadName, true))
if err != nil {
return nil, err
}
Expand Down
39 changes: 39 additions & 0 deletions core/services/relay/evm/chain_writer_historical_wrapper_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package evm

import (
"context"
"math/big"

commontypes "github.com/smartcontractkit/chainlink-common/pkg/types"
interfacetesttypes "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests"
primitives "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives"
)

// This wrapper is required to enable the ChainReader to access historical data
// Since the geth simulated backend doesn't support historical data, we use this
// thin wrapper.
type ChainWriterHistoricalWrapper struct {
commontypes.ChainWriter
cwh *ClientWithContractHistory
}

func NewChainWriterHistoricalWrapper(cw commontypes.ChainWriter, cwh *ClientWithContractHistory) *ChainWriterHistoricalWrapper {
return &ChainWriterHistoricalWrapper{ChainWriter: cw, cwh: cwh}
}

func (cwhw *ChainWriterHistoricalWrapper) SubmitTransaction(ctx context.Context, contractName, method string, args any, transactionID string, toAddress string, meta *commontypes.TxMeta, value *big.Int) error {
if primArgs, ok := args.(interfacetesttypes.PrimitiveArgs); ok {
callArgs := interfacetesttypes.ExpectedGetLatestValueArgs{
ContractName: contractName,
ReadName: "GetAlterablePrimitiveValue",
ConfidenceLevel: primitives.Unconfirmed,
Params: nil,
ReturnVal: nil,
}
err := cwhw.cwh.SetUintLatestValue(ctx, primArgs.Value, callArgs)
if err != nil {
return err
}
}
return cwhw.ChainWriter.SubmitTransaction(ctx, contractName, method, args, transactionID, toAddress, meta, value)
}
Loading
Loading