Skip to content

Commit

Permalink
Merge pull request #11532 from smartcontractkit/rtinianov_multicontra…
Browse files Browse the repository at this point in the history
…cttest

Support contract mapping
  • Loading branch information
nolag authored Dec 12, 2023
2 parents 19e3325 + f2beeb4 commit 9f61865
Show file tree
Hide file tree
Showing 14 changed files with 245 additions and 118 deletions.
2 changes: 1 addition & 1 deletion core/scripts/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ require (
github.com/shirou/gopsutil/v3 v3.23.10 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704 // indirect
github.com/smartcontractkit/chainlink-common v0.1.7-0.20231208164731-2783ddbbdd8c // indirect
github.com/smartcontractkit/chainlink-common v0.1.7-0.20231211005859-c9933df69e15 // indirect
github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231128204301-ee4297eff679 // indirect
github.com/smartcontractkit/chainlink-data-streams v0.0.0-20231204152908-a6e3fe8ff2a1 // indirect
github.com/smartcontractkit/chainlink-feeds v0.0.0-20231127231053-2232d3a6766d // indirect
Expand Down
4 changes: 2 additions & 2 deletions core/scripts/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1214,8 +1214,8 @@ github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704 h1:T3lFWumv
github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704/go.mod h1:2QuJdEouTWjh5BDy5o/vgGXQtR4Gz8yH1IYB5eT7u4M=
github.com/smartcontractkit/chainlink-automation v1.0.1 h1:vVjBFq2Zsz21kPy1Pb0wpjF9zrbJX+zjXphDeeR4XZk=
github.com/smartcontractkit/chainlink-automation v1.0.1/go.mod h1:INSchkV3ntyDdlZKGWA030MPDpp6pbeuiRkRKYFCm2k=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20231208164731-2783ddbbdd8c h1:YgizNDPRAP2ObjetDIQTJlrpzLBBkYsNjA10Mo5HByo=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20231208164731-2783ddbbdd8c/go.mod h1:IdlfCN9rUs8Q/hrOYe8McNBIwEOHEsi0jilb3Cw77xs=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20231211005859-c9933df69e15 h1:do3KwTWj7fatHOMwoRwRWefX0ShtbPzNbsOivgNxW3k=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20231211005859-c9933df69e15/go.mod h1:IdlfCN9rUs8Q/hrOYe8McNBIwEOHEsi0jilb3Cw77xs=
github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231128204301-ee4297eff679 h1:iu1pNbUoSDTrp+7BUtfTygZ2C0f5C2ZOBQhIoJjp+S0=
github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231128204301-ee4297eff679/go.mod h1:2Jx7bTEk4ujFQdsZpZq3A0BydvaVPs6mX8clUfxHOEM=
github.com/smartcontractkit/chainlink-data-streams v0.0.0-20231204152908-a6e3fe8ff2a1 h1:xYqRgZO0nMSO8CBCMR0r3WA+LZ4kNL8a6bnbyk/oBtQ=
Expand Down
46 changes: 46 additions & 0 deletions core/services/relay/evm/bindings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package evm

import (
"fmt"

"github.com/ethereum/go-ethereum/common"

"github.com/smartcontractkit/chainlink-common/pkg/types"
)

type Bindings map[string]methodBindings

func (b Bindings) addEvent(contractName, typeName string, evt common.Hash) error {
ae, err := b.getBinding(contractName, typeName)
if err != nil {
return err
}

ae.evt = &evt
return nil
}

func (b Bindings) getBinding(contractName, typeName string) (*addrEvtBinding, error) {
typeNames, ok := b[contractName]
if !ok {
return nil, fmt.Errorf("%w: contract %s not found", types.ErrInvalidConfig, contractName)
}

ae, ok := typeNames[typeName]
if !ok {
return nil, fmt.Errorf("%w: method %s not found in contract %s", types.ErrInvalidConfig, typeName, contractName)
}

return ae, nil
}

type methodBindings map[string]*addrEvtBinding

func NewAddrEvtFromAddress(address common.Address) *addrEvtBinding {
return &addrEvtBinding{addr: address}
}

type addrEvtBinding struct {
addr common.Address
evt *common.Hash
}
136 changes: 79 additions & 57 deletions core/services/relay/evm/chain_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/pkg/errors"

commonservices "github.com/smartcontractkit/chainlink-common/pkg/services"
commontypes "github.com/smartcontractkit/chainlink-common/pkg/types"
Expand All @@ -29,48 +28,51 @@ type ChainReaderService interface {
}

type chainReader struct {
lggr logger.Logger
lp logpoller.LogPoller
codec commontypes.RemoteCodec
client evmclient.Client
contractID common.Address
events map[string]common.Hash
lggr logger.Logger
lp logpoller.LogPoller
codec commontypes.RemoteCodec
client evmclient.Client
bindings Bindings
commonservices.StateMachine
}

// NewChainReaderService is a constructor for ChainReader, returns nil if there is any error
func NewChainReaderService(lggr logger.Logger, lp logpoller.LogPoller, contractID common.Address, chain legacyevm.Chain, config types.ChainReaderConfig) (ChainReaderService, error) {
func NewChainReaderService(lggr logger.Logger, lp logpoller.LogPoller, b Bindings, chain legacyevm.Chain, config types.ChainReaderConfig) (ChainReaderService, error) {
parsed := &parsedTypes{
encoderDefs: map[string]*codecEntry{},
decoderDefs: map[string]*codecEntry{},
}

events, err := addTypes(config.ChainContractReaders, parsed)
if err != nil {
if err := addTypes(config.ChainContractReaders, b, parsed); err != nil {
return nil, err
}

c, err := parsed.toCodec()

return &chainReader{
lggr: lggr.Named("ChainReader"),
lp: lp,
codec: c,
client: chain.Client(),
contractID: contractID,
events: events,
lggr: lggr.Named("ChainReader"),
lp: lp,
codec: c,
client: chain.Client(),
bindings: b,
}, err
}

func (cr *chainReader) Name() string { return cr.lggr.Name() }

var _ commontypes.TypeProvider = &chainReader{}
var _ commontypes.ContractTypeProvider = &chainReader{}

func (cr *chainReader) GetLatestValue(ctx context.Context, bc commontypes.BoundContract, method string, params any, returnVal any) error {
if hash, ok := cr.events[method]; ok {
return cr.getLatestValueFromLogPoller(ctx, bc, method, hash, returnVal)
b, err := cr.bindings.getBinding(bc.Name, method)
if err != nil {
return err
}

if b.evt == nil {
return cr.getLatestValueFromContract(ctx, bc, method, params, returnVal)
}
return cr.getLatestValueFromContract(ctx, bc, method, params, returnVal)

return cr.getLatestValueFromLogPoller(ctx, bc, method, *b.evt, returnVal)
}

func (cr *chainReader) getLatestValueFromLogPoller(ctx context.Context, bc commontypes.BoundContract, method string, hash common.Hash, returnVal any) error {
Expand All @@ -82,11 +84,11 @@ func (cr *chainReader) getLatestValueFromLogPoller(ctx context.Context, bc commo
}
return fmt.Errorf("%w: %w", commontypes.ErrInternal, err)
}
return cr.codec.Decode(ctx, log.Data, returnVal, wrapItemType(method, false))
return cr.codec.Decode(ctx, log.Data, returnVal, wrapItemType(bc.Name, method, false))
}

func (cr *chainReader) getLatestValueFromContract(ctx context.Context, bc commontypes.BoundContract, method string, params any, returnVal any) error {
data, err := cr.codec.Encode(ctx, params, wrapItemType(method, true))
data, err := cr.codec.Encode(ctx, params, wrapItemType(bc.Name, method, true))
if err != nil {
return err
}
Expand All @@ -104,59 +106,79 @@ func (cr *chainReader) getLatestValueFromContract(ctx context.Context, bc common
return err
}

return cr.codec.Decode(ctx, output, returnVal, wrapItemType(method, false))
return cr.codec.Decode(ctx, output, returnVal, wrapItemType(bc.Name, method, false))
}

func (cr *chainReader) Start(_ context.Context) error {
return cr.StartOnce("ChainReader", func() error {
for name, eventId := range cr.events {
if err := cr.lp.RegisterFilter(logpoller.Filter{
Name: name,
EventSigs: evmtypes.HashArray{eventId},
Addresses: evmtypes.AddressArray{cr.contractID},
}); err != nil {
return fmt.Errorf("%w: %w", commontypes.ErrInternal, err)
for contractName, contractEvents := range cr.bindings {
for eventName, b := range contractEvents {
if b.evt == nil {
continue
}

if err := cr.lp.RegisterFilter(logpoller.Filter{
Name: wrapItemType(contractName, eventName, false),
EventSigs: evmtypes.HashArray{*b.evt},
Addresses: evmtypes.AddressArray{b.addr},
}); err != nil {
return fmt.Errorf("%w: %w", commontypes.ErrInternal, err)
}
}
}
return nil
})
}
func (cr *chainReader) Close() error {
return nil
return cr.StopOnce("ChainReader", func() error {
for contractName, contractEvents := range cr.bindings {
for eventName := range contractEvents {
if err := cr.lp.UnregisterFilter(wrapItemType(contractName, eventName, false)); err != nil {
return fmt.Errorf("%w: %w", commontypes.ErrInternal, err)
}
}
}
return nil
})
}

func (cr *chainReader) Ready() error { return nil }
func (cr *chainReader) HealthReport() map[string]error {
return map[string]error{cr.Name(): nil}
}

func (cr *chainReader) CreateType(itemType string, forEncoding bool) (any, error) {
return cr.codec.CreateType(wrapItemType(itemType, forEncoding), forEncoding)
func (cr *chainReader) CreateContractType(contractName, methodName string, forEncoding bool) (any, error) {
return cr.codec.CreateType(wrapItemType(contractName, methodName, forEncoding), forEncoding)
}

func addEventTypes(name string, contractABI abi.ABI, chainReaderDefinition types.ChainReaderDefinition, parsed *parsedTypes) (common.Hash, error) {
func addEventTypes(contractName, methodName string, b Bindings, contractABI abi.ABI, chainReaderDefinition types.ChainReaderDefinition, parsed *parsedTypes) error {
event, methodExists := contractABI.Events[chainReaderDefinition.ChainSpecificName]
if !methodExists {
return common.Hash{}, fmt.Errorf("method: %s doesn't exist", chainReaderDefinition.ChainSpecificName)
return fmt.Errorf("%w: method %s doesn't exist", commontypes.ErrInvalidConfig, chainReaderDefinition.ChainSpecificName)
}

return event.ID, addDecoderDef(name, event.Inputs, parsed, chainReaderDefinition)
if err := b.addEvent(contractName, methodName, event.ID); err != nil {
return err
}

return addDecoderDef(contractName, methodName, event.Inputs, parsed, chainReaderDefinition)
}

func addMethods(name string, abi abi.ABI, chainReaderDefinition types.ChainReaderDefinition, parsed *parsedTypes) error {
func addMethods(
contractName, methodName string, abi abi.ABI, chainReaderDefinition types.ChainReaderDefinition, parsed *parsedTypes) error {
method, methodExists := abi.Methods[chainReaderDefinition.ChainSpecificName]
if !methodExists {
return fmt.Errorf("method: %q doesn't exist", chainReaderDefinition.ChainSpecificName)
}

if err := addEncoderDef(name, method, parsed, chainReaderDefinition); err != nil {
if err := addEncoderDef(contractName, methodName, method, parsed, chainReaderDefinition); err != nil {
return err
}

return addDecoderDef(name, method.Outputs, parsed, chainReaderDefinition)
return addDecoderDef(contractName, methodName, method.Outputs, parsed, chainReaderDefinition)
}

func addEncoderDef(name string, method abi.Method, parsed *parsedTypes, chainReaderDefinition types.ChainReaderDefinition) error {
func addEncoderDef(contractName, methodName string, method abi.Method, parsed *parsedTypes, chainReaderDefinition types.ChainReaderDefinition) error {
// ABI.Pack prepends the method.ID to the encodings, we'll need the encoder to do the same.
input := &codecEntry{Args: method.Inputs, encodingPrefix: method.ID}

Expand All @@ -169,53 +191,53 @@ func addEncoderDef(name string, method abi.Method, parsed *parsedTypes, chainRea
return err
}
input.mod = inputMod
parsed.encoderDefs[wrapItemType(name, true)] = input
parsed.encoderDefs[wrapItemType(contractName, methodName, true)] = input
return nil
}

func addDecoderDef(name string, outputs abi.Arguments, parsed *parsedTypes, def types.ChainReaderDefinition) error {
func addDecoderDef(contractName, methodName string, outputs abi.Arguments, parsed *parsedTypes, def types.ChainReaderDefinition) error {
output := &codecEntry{Args: outputs}
mod, err := def.OutputModifications.ToModifier(evmDecoderHooks...)
if err != nil {
return err
}
output.mod = mod
parsed.decoderDefs[wrapItemType(name, false)] = output
parsed.decoderDefs[wrapItemType(contractName, methodName, false)] = output
return output.Init()
}

func addTypes(chainContractReaders map[string]types.ChainContractReader, parsed *parsedTypes) (map[string]common.Hash, error) {
events := map[string]common.Hash{}
func addTypes(chainContractReaders map[string]types.ChainContractReader, b Bindings, parsed *parsedTypes) error {
for contractName, chainContractReader := range chainContractReaders {
contractAbi, err := abi.JSON(strings.NewReader(chainContractReader.ContractABI))
if err != nil {
return nil, err
return err
}

for chainReadingDefinitionName, chainReaderDefinition := range chainContractReader.ChainReaderDefinitions {
for typeName, chainReaderDefinition := range chainContractReader.ChainReaderDefinitions {
switch chainReaderDefinition.ReadType {
case types.Method:
err = addMethods(chainReadingDefinitionName, contractAbi, chainReaderDefinition, parsed)
err = addMethods(contractName, typeName, contractAbi, chainReaderDefinition, parsed)
case types.Event:
var hash common.Hash
hash, err = addEventTypes(chainReadingDefinitionName, contractAbi, chainReaderDefinition, parsed)
events[chainReadingDefinitionName] = hash
err = addEventTypes(contractName, typeName, b, contractAbi, chainReaderDefinition, parsed)
default:
return nil, fmt.Errorf("invalid chain reader definition read type: %d", chainReaderDefinition.ReadType)
return fmt.Errorf(
"%w: invalid chain reader definition read type: %d",
commontypes.ErrInvalidConfig,
chainReaderDefinition.ReadType)
}

if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("invalid chain reader config for contract: %q chain reading definition: %q", contractName, chainReadingDefinitionName))
return err
}
}
}

return events, nil
return nil
}

func wrapItemType(itemType string, isParams bool) string {
func wrapItemType(contractName, methodName string, isParams bool) string {
if isParams {
return fmt.Sprintf("params.%s", itemType)
return fmt.Sprintf("params.%s.%s", contractName, methodName)
}
return fmt.Sprintf("return.%s", itemType)
return fmt.Sprintf("return.%s.%s", contractName, methodName)
}
Loading

0 comments on commit 9f61865

Please sign in to comment.