Skip to content

Commit

Permalink
[BCI-3361] Pre-parse contract ABIs in ChainWriter (#13381)
Browse files Browse the repository at this point in the history
* evm: Pre-parse contract abis and use the encoder in chainwriter

* .changeset: Add a changeset

* evm: Modify the input arguments of chain writer to be a single param instead of a slice

* evm: Add code comments noting the bug in the codec library

* evm: Remove debug logs

* go.mod: Bump chainlink-common dep

* chainlin: Run gomodtidy

* fixed mockery version issues

---------

Co-authored-by: Silas Lenihan <[email protected]>
  • Loading branch information
nickcorin and silaslenihan authored May 31, 2024
1 parent 390ee19 commit 0a62f02
Show file tree
Hide file tree
Showing 14 changed files with 131 additions and 47 deletions.
5 changes: 5 additions & 0 deletions .changeset/gentle-walls-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

#internal Pre-process contract abis in the evm chainwriter.
4 changes: 2 additions & 2 deletions core/capabilities/targets/mocks/chain_writer.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 24 additions & 2 deletions core/capabilities/targets/write_target.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,32 @@ func (cap *WriteTarget) Execute(ctx context.Context, request capabilities.Capabi
if err != nil {
return nil, err
}
args := []any{common.HexToAddress(reqConfig.Address), inputs.Report, inputs.Context, inputs.Signatures}

// Note: The codec that ChainWriter uses to encode the parameters for the contract ABI cannot handle
// `nil` values, including for slices. Until the bug is fixed we need to ensure that there are no
// `nil` values passed in the request.
req := struct {
ReceiverAddress string
RawReport []byte
ReportContext []byte
Signatures [][]byte
}{reqConfig.Address, inputs.Report, inputs.Context, inputs.Signatures}

if req.RawReport == nil {
req.RawReport = make([]byte, 0)
}

if req.ReportContext == nil {
req.ReportContext = make([]byte, 0)
}

if req.Signatures == nil {
req.Signatures = make([][]byte, 0)
}

meta := commontypes.TxMeta{WorkflowExecutionID: &request.Metadata.WorkflowExecutionID}
value := big.NewInt(0)
if err := cap.cw.SubmitTransaction(ctx, "forwarder", "report", args, txID, cap.forwarderAddress, &meta, *value); err != nil {
if err := cap.cw.SubmitTransaction(ctx, "forwarder", "report", req, txID, cap.forwarderAddress, &meta, *value); err != nil {
return nil, err
}
cap.lggr.Debugw("Transaction submitted", "request", request, "transaction", txID)
Expand Down
2 changes: 1 addition & 1 deletion core/scripts/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ require (
github.com/prometheus/client_golang v1.17.0
github.com/shopspring/decimal v1.3.1
github.com/smartcontractkit/chainlink-automation v1.0.3
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531161624-ddf27dafed93
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531185627-1ff5b506c5f9
github.com/smartcontractkit/chainlink-vrf v0.0.0-20240222010609-cd67d123c772
github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000
github.com/smartcontractkit/libocr v0.0.0-20240419185742-fd3cab206b2c
Expand Down
4 changes: 2 additions & 2 deletions core/scripts/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1185,8 +1185,8 @@ github.com/smartcontractkit/chain-selectors v1.0.10 h1:t9kJeE6B6G+hKD0GYR4kGJSCq
github.com/smartcontractkit/chain-selectors v1.0.10/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE=
github.com/smartcontractkit/chainlink-automation v1.0.3 h1:h/ijT0NiyV06VxYVgcNfsE3+8OEzT3Q0Z9au0z1BPWs=
github.com/smartcontractkit/chainlink-automation v1.0.3/go.mod h1:RjboV0Qd7YP+To+OrzHGXaxUxoSONveCoAK2TQ1INLU=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531161624-ddf27dafed93 h1:LEukS7+ZNK61mfmZiieD2csK52n/RGg+M60LCuc6zPQ=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531161624-ddf27dafed93/go.mod h1:DUZccDEW98n+J1mhdWGO7wr/Njad9p9Fzks839JN7Rs=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531185627-1ff5b506c5f9 h1:6KxgmkZdVE/nU5ym5j114MELSs13HgMy5FV0TpnocS8=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531185627-1ff5b506c5f9/go.mod h1:DUZccDEW98n+J1mhdWGO7wr/Njad9p9Fzks839JN7Rs=
github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240524214833-c362c2ebbd2d h1:5tgMC5Gi2UAOKZ+m28W8ubjLeR0pQCAcrz6eQ0rW510=
github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240524214833-c362c2ebbd2d/go.mod h1:0UNuO3nDt9MFsZPaHJBEUolxVkN0iC69j1ccDp95e8k=
github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240220203239-09be0ea34540 h1:xFSv8561jsLtF6gYZr/zW2z5qUUAkcFkApin2mnbYTo=
Expand Down
99 changes: 76 additions & 23 deletions core/services/relay/evm/chain_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import (
"context"
"fmt"
"math/big"
"time"
"strings"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/google/uuid"

Expand All @@ -14,7 +15,6 @@ import (
txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types"
evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client"
evmtxmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr"
evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types"
"github.com/smartcontractkit/chainlink/v2/core/logger"
"github.com/smartcontractkit/chainlink/v2/core/services"
"github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types"
Expand All @@ -30,26 +30,58 @@ type ChainWriterService interface {
// Compile-time assertion that chainWriter implements the ChainWriterService interface.
var _ ChainWriterService = (*chainWriter)(nil)

func NewChainWriterService(logger logger.Logger, client evmclient.Client, txm evmtxmgr.TxManager, config types.ChainWriterConfig) ChainWriterService {
return &chainWriter{logger: logger, client: client, config: config, txm: txm}
func NewChainWriterService(logger logger.Logger, client evmclient.Client, txm evmtxmgr.TxManager, config types.ChainWriterConfig) (ChainWriterService, error) {
w := chainWriter{
logger: logger,
client: client,
txm: txm,

sendStrategy: txmgr.NewSendEveryStrategy(),
contracts: config.Contracts,
parsedContracts: &parsedTypes{encoderDefs: map[string]types.CodecEntry{}, decoderDefs: map[string]types.CodecEntry{}},
}

if config.SendStrategy != nil {
w.sendStrategy = config.SendStrategy
}

if err := w.parseContracts(); err != nil {
return nil, fmt.Errorf("%w: failed to parse contracts", err)
}

var err error
if w.encoder, err = w.parsedContracts.toCodec(); err != nil {
return nil, fmt.Errorf("%w: failed to create codec", err)
}

return &w, nil
}

type chainWriter struct {
commonservices.StateMachine

logger logger.Logger
client evmclient.Client
config types.ChainWriterConfig
txm evmtxmgr.TxManager

sendStrategy txmgrtypes.TxStrategy
contracts map[string]*types.ContractConfig
parsedContracts *parsedTypes

encoder commontypes.Encoder
}

func (w *chainWriter) SubmitTransaction(ctx context.Context, contract, method string, args []any, transactionID uuid.UUID, toAddress string, meta *commontypes.TxMeta, value big.Int) error {
// SubmitTransaction ...
//
// Note: The codec that ChainWriter uses to encode the parameters for the contract ABI cannot handle
// `nil` values, including for slices. Until the bug is fixed we need to ensure that there are no
// `nil` values passed in the request.
func (w *chainWriter) SubmitTransaction(ctx context.Context, contract, method string, args any, transactionID uuid.UUID, toAddress string, meta *commontypes.TxMeta, value big.Int) error {
if !common.IsHexAddress(toAddress) {
return fmt.Errorf("toAddress is not a valid ethereum address: %v", toAddress)
}

// TODO(nickcorin): Pre-process the contracts when initialising the chain writer.
contractConfig, ok := w.config.Contracts[contract]
contractConfig, ok := w.contracts[contract]
if !ok {
return fmt.Errorf("contract config not found: %v", contract)
}
Expand All @@ -59,36 +91,61 @@ func (w *chainWriter) SubmitTransaction(ctx context.Context, contract, method st
return fmt.Errorf("method config not found: %v", method)
}

forwarderABI := evmtypes.MustGetABI(contractConfig.ContractABI)

calldata, err := forwarderABI.Pack(methodConfig.ChainSpecificName, args...)
calldata, err := w.encoder.Encode(ctx, args, wrapItemType(contract, method, true))
if err != nil {
return fmt.Errorf("pack forwarder abi: %w", err)
return fmt.Errorf("%w: failed to encode args", err)
}

var checker evmtxmgr.TransmitCheckerSpec
if methodConfig.Checker != "" {
checker.CheckerType = txmgrtypes.TransmitCheckerType(methodConfig.Checker)
}

var sendStrategy txmgrtypes.TxStrategy = txmgr.SendEveryStrategy{}
if w.config.SendStrategy != nil {
sendStrategy = w.config.SendStrategy
}

req := evmtxmgr.TxRequest{
FromAddress: methodConfig.FromAddress,
ToAddress: common.HexToAddress(toAddress),
EncodedPayload: calldata,
FeeLimit: methodConfig.GasLimit,
Meta: &txmgrtypes.TxMeta[common.Address, common.Hash]{WorkflowExecutionID: meta.WorkflowExecutionID},
Strategy: sendStrategy,
Strategy: w.sendStrategy,
Checker: checker,
}

_, err = w.txm.CreateTransaction(ctx, req)
if err != nil {
return fmt.Errorf("failed to create tx: %w", err)
return fmt.Errorf("%w; failed to create tx", err)
}

return nil
}

func (w *chainWriter) parseContracts() error {
for contract, contractConfig := range w.contracts {
abi, err := abi.JSON(strings.NewReader(contractConfig.ContractABI))
if err != nil {
return fmt.Errorf("%w: failed to parse contract abi", err)
}

for method, methodConfig := range contractConfig.Configs {
abiMethod, ok := abi.Methods[methodConfig.ChainSpecificName]
if !ok {
return fmt.Errorf("%w: method %s doesn't exist", commontypes.ErrInvalidConfig, methodConfig.ChainSpecificName)
}

// ABI.Pack prepends the method.ID to the encodings, we'll need the encoder to do the same.
inputMod, err := methodConfig.InputModifications.ToModifier(evmDecoderHooks...)
if err != nil {
return fmt.Errorf("%w: failed to create input mods", err)
}

input := types.NewCodecEntry(abiMethod.Inputs, abiMethod.ID, inputMod)

if err = input.Init(); err != nil {
return fmt.Errorf("%w: failed to init codec entry for method %s", err, method)
}

w.parsedContracts.encoderDefs[wrapItemType(contract, method, true)] = input
}
}

return nil
Expand All @@ -104,10 +161,6 @@ func (w *chainWriter) GetFeeComponents(ctx context.Context) (*commontypes.ChainF

func (w *chainWriter) Close() error {
return w.StopOnce(w.Name(), func() error {
_, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

// TODO(nickcorin): Add shutdown steps here.
return nil
})
}
Expand Down
13 changes: 7 additions & 6 deletions core/services/relay/evm/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,23 @@ import (
)

type ChainWriterConfig struct {
Contracts map[string]*ContractConfig
SendStrategy txmgrtypes.TxStrategy
Contracts map[string]ChainWriter
}

type ChainWriter struct {
type ContractConfig struct {
ContractABI string `json:"contractABI" toml:"contractABI"`
// key is genericName from config
Configs map[string]*ChainWriterDefinition `json:"configs" toml:"configs"`
}

type ChainWriterDefinition struct {
// chain specific contract method name or event type.
ChainSpecificName string `json:"chainSpecificName"`
Checker string `json:"checker"`
FromAddress common.Address `json:"fromAddress"`
GasLimit uint64 `json:"gasLimit"` // TODO(archseer): what if this has to be configured per call?
ChainSpecificName string `json:"chainSpecificName"`
Checker string `json:"checker"`
FromAddress common.Address `json:"fromAddress"`
GasLimit uint64 `json:"gasLimit"` // TODO(archseer): what if this has to be configured per call?
InputModifications codec.ModifiersConfig `json:"inputModifications,omitempty"`
}

type ChainReaderConfig struct {
Expand Down
7 changes: 5 additions & 2 deletions core/services/relay/evm/write_target.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func NewWriteTarget(ctx context.Context, relayer *Relayer, chain legacyevm.Chain
}

chainWriterConfig := relayevmtypes.ChainWriterConfig{
Contracts: map[string]relayevmtypes.ChainWriter{
Contracts: map[string]*relayevmtypes.ContractConfig{
"forwarder": {
ContractABI: forwarder.KeystoneForwarderABI,
Configs: map[string]*relayevmtypes.ChainWriterDefinition{
Expand All @@ -69,7 +69,10 @@ func NewWriteTarget(ctx context.Context, relayer *Relayer, chain legacyevm.Chain
},
},
}
cw := NewChainWriterService(lggr.Named("ChainWriter"), chain.Client(), chain.TxManager(), chainWriterConfig)
cw, err := NewChainWriterService(lggr.Named("ChainWriter"), chain.Client(), chain.TxManager(), chainWriterConfig)
if err != nil {
return nil, err
}

return targets.NewWriteTarget(lggr, name, cr, cw, config.ForwarderAddress().String()), nil
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ require (
github.com/shopspring/decimal v1.3.1
github.com/smartcontractkit/chain-selectors v1.0.10
github.com/smartcontractkit/chainlink-automation v1.0.3
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531161624-ddf27dafed93
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531185627-1ff5b506c5f9
github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240524214833-c362c2ebbd2d
github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240220203239-09be0ea34540
github.com/smartcontractkit/chainlink-feeds v0.0.0-20240522213638-159fb2d99917
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1171,8 +1171,8 @@ github.com/smartcontractkit/chain-selectors v1.0.10 h1:t9kJeE6B6G+hKD0GYR4kGJSCq
github.com/smartcontractkit/chain-selectors v1.0.10/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE=
github.com/smartcontractkit/chainlink-automation v1.0.3 h1:h/ijT0NiyV06VxYVgcNfsE3+8OEzT3Q0Z9au0z1BPWs=
github.com/smartcontractkit/chainlink-automation v1.0.3/go.mod h1:RjboV0Qd7YP+To+OrzHGXaxUxoSONveCoAK2TQ1INLU=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531161624-ddf27dafed93 h1:LEukS7+ZNK61mfmZiieD2csK52n/RGg+M60LCuc6zPQ=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531161624-ddf27dafed93/go.mod h1:DUZccDEW98n+J1mhdWGO7wr/Njad9p9Fzks839JN7Rs=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531185627-1ff5b506c5f9 h1:6KxgmkZdVE/nU5ym5j114MELSs13HgMy5FV0TpnocS8=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531185627-1ff5b506c5f9/go.mod h1:DUZccDEW98n+J1mhdWGO7wr/Njad9p9Fzks839JN7Rs=
github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240524214833-c362c2ebbd2d h1:5tgMC5Gi2UAOKZ+m28W8ubjLeR0pQCAcrz6eQ0rW510=
github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240524214833-c362c2ebbd2d/go.mod h1:0UNuO3nDt9MFsZPaHJBEUolxVkN0iC69j1ccDp95e8k=
github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240220203239-09be0ea34540 h1:xFSv8561jsLtF6gYZr/zW2z5qUUAkcFkApin2mnbYTo=
Expand Down
2 changes: 1 addition & 1 deletion integration-tests/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ require (
github.com/shopspring/decimal v1.3.1
github.com/slack-go/slack v0.12.2
github.com/smartcontractkit/chainlink-automation v1.0.3
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531161624-ddf27dafed93
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531185627-1ff5b506c5f9
github.com/smartcontractkit/chainlink-testing-framework v1.28.17
github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868
github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000
Expand Down
4 changes: 2 additions & 2 deletions integration-tests/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1512,8 +1512,8 @@ github.com/smartcontractkit/chain-selectors v1.0.10 h1:t9kJeE6B6G+hKD0GYR4kGJSCq
github.com/smartcontractkit/chain-selectors v1.0.10/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE=
github.com/smartcontractkit/chainlink-automation v1.0.3 h1:h/ijT0NiyV06VxYVgcNfsE3+8OEzT3Q0Z9au0z1BPWs=
github.com/smartcontractkit/chainlink-automation v1.0.3/go.mod h1:RjboV0Qd7YP+To+OrzHGXaxUxoSONveCoAK2TQ1INLU=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531161624-ddf27dafed93 h1:LEukS7+ZNK61mfmZiieD2csK52n/RGg+M60LCuc6zPQ=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531161624-ddf27dafed93/go.mod h1:DUZccDEW98n+J1mhdWGO7wr/Njad9p9Fzks839JN7Rs=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531185627-1ff5b506c5f9 h1:6KxgmkZdVE/nU5ym5j114MELSs13HgMy5FV0TpnocS8=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531185627-1ff5b506c5f9/go.mod h1:DUZccDEW98n+J1mhdWGO7wr/Njad9p9Fzks839JN7Rs=
github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240524214833-c362c2ebbd2d h1:5tgMC5Gi2UAOKZ+m28W8ubjLeR0pQCAcrz6eQ0rW510=
github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240524214833-c362c2ebbd2d/go.mod h1:0UNuO3nDt9MFsZPaHJBEUolxVkN0iC69j1ccDp95e8k=
github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240220203239-09be0ea34540 h1:xFSv8561jsLtF6gYZr/zW2z5qUUAkcFkApin2mnbYTo=
Expand Down
2 changes: 1 addition & 1 deletion integration-tests/load/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ require (
github.com/rs/zerolog v1.30.0
github.com/slack-go/slack v0.12.2
github.com/smartcontractkit/chainlink-automation v1.0.3
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531161624-ddf27dafed93
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531185627-1ff5b506c5f9
github.com/smartcontractkit/chainlink-testing-framework v1.28.17
github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20240214231432-4ad5eb95178c
github.com/smartcontractkit/chainlink/v2 v2.9.0-beta0.0.20240216210048-da02459ddad8
Expand Down
4 changes: 2 additions & 2 deletions integration-tests/load/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1502,8 +1502,8 @@ github.com/smartcontractkit/chain-selectors v1.0.10 h1:t9kJeE6B6G+hKD0GYR4kGJSCq
github.com/smartcontractkit/chain-selectors v1.0.10/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE=
github.com/smartcontractkit/chainlink-automation v1.0.3 h1:h/ijT0NiyV06VxYVgcNfsE3+8OEzT3Q0Z9au0z1BPWs=
github.com/smartcontractkit/chainlink-automation v1.0.3/go.mod h1:RjboV0Qd7YP+To+OrzHGXaxUxoSONveCoAK2TQ1INLU=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531161624-ddf27dafed93 h1:LEukS7+ZNK61mfmZiieD2csK52n/RGg+M60LCuc6zPQ=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531161624-ddf27dafed93/go.mod h1:DUZccDEW98n+J1mhdWGO7wr/Njad9p9Fzks839JN7Rs=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531185627-1ff5b506c5f9 h1:6KxgmkZdVE/nU5ym5j114MELSs13HgMy5FV0TpnocS8=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240531185627-1ff5b506c5f9/go.mod h1:DUZccDEW98n+J1mhdWGO7wr/Njad9p9Fzks839JN7Rs=
github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240524214833-c362c2ebbd2d h1:5tgMC5Gi2UAOKZ+m28W8ubjLeR0pQCAcrz6eQ0rW510=
github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240524214833-c362c2ebbd2d/go.mod h1:0UNuO3nDt9MFsZPaHJBEUolxVkN0iC69j1ccDp95e8k=
github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240220203239-09be0ea34540 h1:xFSv8561jsLtF6gYZr/zW2z5qUUAkcFkApin2mnbYTo=
Expand Down

0 comments on commit 0a62f02

Please sign in to comment.