From f053dbabbf33bd581f98bd2a3714dde6aa414f90 Mon Sep 17 00:00:00 2001 From: jinhoonbang Date: Thu, 8 Feb 2024 20:10:53 -0800 Subject: [PATCH 01/10] allow 0 confirmation delays in VRF; use pending block for simulation in VRF --- .../evm/client/simulated_backend_client.go | 76 ++++++++- .../testnet/v2plusscripts/super_scripts.go | 27 +++- .../vrfv2plus/testnet/v2plusscripts/util.go | 10 +- core/services/pipeline/common.go | 11 ++ core/services/pipeline/task.estimategas.go | 30 +++- core/services/pipeline/task.eth_call.go | 40 +++-- core/services/pipeline/task.eth_call_test.go | 149 +++++++++++++++--- core/services/pipeline/task.vrfv2plus.go | 1 + core/services/vrf/vrfcommon/validate.go | 3 - core/testdata/testspecs/v2_specs.go | 2 + integration-tests/client/chainlink_models.go | 6 +- 11 files changed, 289 insertions(+), 66 deletions(-) diff --git a/core/chains/evm/client/simulated_backend_client.go b/core/chains/evm/client/simulated_backend_client.go index bd2e959d9bc..918f5ce4045 100644 --- a/core/chains/evm/client/simulated_backend_client.go +++ b/core/chains/evm/client/simulated_backend_client.go @@ -102,6 +102,8 @@ func (c *SimulatedBackendClient) CallContext(ctx context.Context, result interfa return c.ethCall(ctx, result, args...) case "eth_getHeaderByNumber": return c.ethGetHeaderByNumber(ctx, result, args...) + case "eth_estimateGas": + return c.ethEstimateGas(ctx, result, args...) default: return fmt.Errorf("second arg to SimulatedBackendClient.Call is an RPC API method which has not yet been implemented: %s. Add processing for it here", method) } @@ -443,6 +445,8 @@ func (c *SimulatedBackendClient) BatchCallContext(ctx context.Context, b []rpc.B b[i].Error = c.ethCall(ctx, b[i].Result, b[i].Args...) case "eth_getHeaderByNumber": b[i].Error = c.ethGetHeaderByNumber(ctx, b[i].Result, b[i].Args...) + case "eth_estimateGas": + b[i].Error = c.ethEstimateGas(ctx, b[i].Result, b[i].Args...) default: return fmt.Errorf("SimulatedBackendClient got unsupported method %s", elem.Method) } @@ -562,6 +566,37 @@ func (c *SimulatedBackendClient) ethGetBlockByNumber(ctx context.Context, result return nil } +func (c *SimulatedBackendClient) ethEstimateGas(ctx context.Context, result interface{}, args ...interface{}) error { + if len(args) != 2 { + return fmt.Errorf("SimulatedBackendClient expected 2 args, got %d for eth_call", len(args)) + } + + params, ok := args[0].(map[string]interface{}) + if !ok { + return fmt.Errorf("SimulatedBackendClient expected first arg to be map[string]interface{} for eth_call, got: %T", args[0]) + } + + _, err := c.blockNumber(args[1]) + if err != nil { + return fmt.Errorf("SimulatedBackendClient expected second arg to be the string 'latest' or a *big.Int for eth_call, got: %T", args[1]) + } + + resp, err := c.b.EstimateGas(ctx, toCallMsg(params)) + if err != nil { + return err + } + + switch typedResult := result.(type) { + case *uint64: + *typedResult = resp + case *hexutil.Uint64: + *typedResult = hexutil.Uint64(resp) + default: + return fmt.Errorf("SimulatedBackendClient unexpected type %T", result) + } + + return nil +} func (c *SimulatedBackendClient) ethCall(ctx context.Context, result interface{}, args ...interface{}) error { if len(args) != 2 { @@ -625,7 +660,6 @@ func (c *SimulatedBackendClient) ethGetHeaderByNumber(ctx context.Context, resul func toCallMsg(params map[string]interface{}) ethereum.CallMsg { var callMsg ethereum.CallMsg - toAddr, err := interfaceToAddress(params["to"]) if err != nil { panic(fmt.Errorf("unexpected 'to' parameter: %s", err)) @@ -645,6 +679,10 @@ func toCallMsg(params map[string]interface{}) ethereum.CallMsg { callMsg.From = common.HexToAddress("0x") } + if params["data"] != nil && params["input"] != nil { + panic("cannot have both 'data' and 'input' parameters") + } + switch data := params["data"].(type) { case nil: // This parameter is not required so nil is acceptable @@ -656,16 +694,41 @@ func toCallMsg(params map[string]interface{}) ethereum.CallMsg { panic("unexpected type of 'data' parameter; try hexutil.Bytes, []byte, or nil") } + switch input := params["input"].(type) { + case nil: + // This parameter is not required so nil is acceptable + case hexutil.Bytes: + callMsg.Data = input + case []byte: + callMsg.Data = input + default: + panic("unexpected type of 'input' parameter; try hexutil.Bytes, []byte, or nil") + } + if value, ok := params["value"].(*big.Int); ok { callMsg.Value = value } - if gas, ok := params["gas"].(uint64); ok { + switch gas := params["gas"].(type) { + case nil: + // This parameter is not required so nil is acceptable + case uint64: callMsg.Gas = gas + case hexutil.Uint64: + callMsg.Gas = uint64(gas) + default: + panic("unexpected type of 'gas' parameter; try hexutil.Uint64, or uint64") } - if gasPrice, ok := params["gasPrice"].(*big.Int); ok { + switch gasPrice := params["gasPrice"].(type) { + case nil: + // This parameter is not required so nil is acceptable + case *big.Int: callMsg.GasPrice = gasPrice + case *hexutil.Big: + callMsg.GasPrice = gasPrice.ToInt() + default: + panic("unexpected type of 'gasPrice' parameter; try *big.Int, or *hexutil.Big") } return callMsg @@ -675,6 +738,11 @@ func interfaceToAddress(value interface{}) (common.Address, error) { switch v := value.(type) { case common.Address: return v, nil + case *common.Address: + if v == nil { + return common.Address{}, nil + } + return *v, nil case string: if ok := common.IsHexAddress(v); !ok { return common.Address{}, fmt.Errorf("string not formatted as a hex encoded evm address") @@ -688,6 +756,6 @@ func interfaceToAddress(value interface{}) (common.Address, error) { return common.BigToAddress(v), nil default: - return common.Address{}, fmt.Errorf("unrecognized value type for converting value to common.Address; use hex encoded string, *big.Int, or common.Address") + return common.Address{}, fmt.Errorf("unrecognized value type: %T for converting value to common.Address; use hex encoded string, *big.Int, or common.Address", v) } } diff --git a/core/scripts/vrfv2plus/testnet/v2plusscripts/super_scripts.go b/core/scripts/vrfv2plus/testnet/v2plusscripts/super_scripts.go index 9f7b708228d..327c959f90d 100644 --- a/core/scripts/vrfv2plus/testnet/v2plusscripts/super_scripts.go +++ b/core/scripts/vrfv2plus/testnet/v2plusscripts/super_scripts.go @@ -472,6 +472,7 @@ func DeployUniverseViaCLI(e helpers.Environment) { deployCmd := flag.NewFlagSet("deploy-universe", flag.ExitOnError) // required flags + nativeOnly := deployCmd.Bool("native-only", false, "if true, link and link feed are not set up") linkAddress := deployCmd.String("link-address", "", "address of link token") linkEthAddress := deployCmd.String("link-eth-feed", "", "address of link eth feed") bhsContractAddressString := deployCmd.String("bhs-address", "", "address of BHS contract") @@ -507,6 +508,15 @@ func DeployUniverseViaCLI(e helpers.Environment) { deployCmd, os.Args[2:], ) + if *nativeOnly { + if *linkAddress != "" || *linkEthAddress != "" { + panic("native-only flag is set, but link address or link eth address is provided") + } + if *subscriptionBalanceJuelsString != "0" { + panic("native-only flag is set, but link subscription balance is provided") + } + } + fallbackWeiPerUnitLink := decimal.RequireFromString(*fallbackWeiPerUnitLinkString).BigInt() subscriptionBalanceJuels := decimal.RequireFromString(*subscriptionBalanceJuelsString).BigInt() subscriptionBalanceNativeWei := decimal.RequireFromString(*subscriptionBalanceNativeWeiString).BigInt() @@ -569,6 +579,8 @@ func DeployUniverseViaCLI(e helpers.Environment) { vrfKeyRegistrationConfig, contractAddresses, coordinatorConfig, + *batchFulfillmentEnabled, + *nativeOnly, nodesMap, uint64(*gasLaneMaxGas), coordinatorJobSpecConfig, @@ -587,6 +599,8 @@ func VRFV2PlusDeployUniverse(e helpers.Environment, vrfKeyRegistrationConfig model.VRFKeyRegistrationConfig, contractAddresses model.ContractAddresses, coordinatorConfig CoordinatorConfigV2Plus, + batchFulfillmentEnabled bool, + nativeOnly bool, nodesMap map[string]model.Node, gasLaneMaxGas uint64, coordinatorJobSpecConfig model.CoordinatorJobSpecConfig, @@ -618,12 +632,12 @@ func VRFV2PlusDeployUniverse(e helpers.Environment, helpers.PanicErr(err) } - if len(contractAddresses.LinkAddress) == 0 { + if !nativeOnly && len(contractAddresses.LinkAddress) == 0 { fmt.Println("\nDeploying LINK Token...") contractAddresses.LinkAddress = helpers.DeployLinkToken(e).String() } - if len(contractAddresses.LinkEthAddress) == 0 { + if !nativeOnly && len(contractAddresses.LinkEthAddress) == 0 { fmt.Println("\nDeploying LINK/ETH Feed...") contractAddresses.LinkEthAddress = helpers.DeployLinkEthFeed(e, contractAddresses.LinkAddress, coordinatorConfig.FallbackWeiPerUnitLink).String() } @@ -715,11 +729,10 @@ func VRFV2PlusDeployUniverse(e helpers.Environment, formattedVrfV2PlusPrimaryJobSpec := fmt.Sprintf( jobs.VRFV2PlusJobFormatted, - contractAddresses.CoordinatorAddress, //coordinatorAddress - contractAddresses.BatchCoordinatorAddress, //batchCoordinatorAddress - coordinatorJobSpecConfig.BatchFulfillmentEnabled, //batchFulfillmentEnabled - coordinatorJobSpecConfig.BatchFulfillmentGasMultiplier, //batchFulfillmentGasMultiplier - strings.Join(util.MapToAddressArr(nodesMap[model.VRFPrimaryNodeName].SendingKeys), "\",\""), //fromAddresses + contractAddresses.CoordinatorAddress, //coordinatorAddress + contractAddresses.BatchCoordinatorAddress, //batchCoordinatorAddress + coordinatorJobSpecConfig.BatchFulfillmentEnabled, //batchFulfillmentEnabled + coordinatorJobSpecConfig.BatchFulfillmentGasMultiplier, //batchFulfillmentGasMultiplier compressedPkHex, //publicKey coordinatorConfig.MinConfs, //minIncomingConfirmations e.ChainID, //evmChainID diff --git a/core/scripts/vrfv2plus/testnet/v2plusscripts/util.go b/core/scripts/vrfv2plus/testnet/v2plusscripts/util.go index 213a2f0dcff..716e0058ff4 100644 --- a/core/scripts/vrfv2plus/testnet/v2plusscripts/util.go +++ b/core/scripts/vrfv2plus/testnet/v2plusscripts/util.go @@ -53,10 +53,12 @@ func DeployCoordinator( coordinator, err := vrf_coordinator_v2_5.NewVRFCoordinatorV25(coordinatorAddress, e.Ec) helpers.PanicErr(err) - linkTx, err := coordinator.SetLINKAndLINKNativeFeed(e.Owner, - common.HexToAddress(linkAddress), common.HexToAddress(linkEthAddress)) - helpers.PanicErr(err) - helpers.ConfirmTXMined(context.Background(), e.Ec, linkTx, e.ChainID) + if linkAddress != "" && linkEthAddress != "" { + linkTx, err := coordinator.SetLINKAndLINKNativeFeed(e.Owner, + common.HexToAddress(linkAddress), common.HexToAddress(linkEthAddress)) + helpers.PanicErr(err) + helpers.ConfirmTXMined(context.Background(), e.Ec, linkTx, e.ChainID) + } return coordinatorAddress } diff --git a/core/services/pipeline/common.go b/core/services/pipeline/common.go index 6efa7aa2148..4e6b8884cde 100644 --- a/core/services/pipeline/common.go +++ b/core/services/pipeline/common.go @@ -671,3 +671,14 @@ func getJsonNumberValue(value json.Number) (interface{}, error) { return result, nil } + +func selectBlock(block string) (string, error) { + if block == "" { + return "latest", nil + } + block = strings.ToLower(block) + if block == "pending" || block == "latest" { + return block, nil + } + return "", pkgerrors.Errorf("unsupported block param: %s", block) +} diff --git a/core/services/pipeline/task.estimategas.go b/core/services/pipeline/task.estimategas.go index 43c148b287f..8fccc5e8eac 100644 --- a/core/services/pipeline/task.estimategas.go +++ b/core/services/pipeline/task.estimategas.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/pkg/errors" "github.com/shopspring/decimal" "go.uber.org/multierr" @@ -28,6 +29,7 @@ type EstimateGasLimitTask struct { Multiplier string `json:"multiplier"` Data string `json:"data"` EVMChainID string `json:"evmChainID" mapstructure:"evmChainID"` + Block string `json:"block"` specGasLimit *uint32 legacyChains legacyevm.LegacyChainContainer @@ -61,6 +63,7 @@ func (t *EstimateGasLimitTask) Run(ctx context.Context, lggr logger.Logger, vars data BytesParam multiplier DecimalParam chainID StringParam + block StringParam ) err := multierr.Combine( errors.Wrap(ResolveParam(&fromAddr, From(VarExpr(t.From, vars), utils.ZeroAddress)), "from"), @@ -69,6 +72,7 @@ func (t *EstimateGasLimitTask) Run(ctx context.Context, lggr logger.Logger, vars // Default to 1, i.e. exactly what estimateGas suggests errors.Wrap(ResolveParam(&multiplier, From(VarExpr(t.Multiplier, vars), NonemptyString(t.Multiplier), decimal.New(1, 0))), "multiplier"), errors.Wrap(ResolveParam(&chainID, From(VarExpr(t.getEvmChainID(), vars), NonemptyString(t.getEvmChainID()), "")), "evmChainID"), + errors.Wrap(ResolveParam(&block, From(VarExpr(t.Block, vars), t.Block)), "block"), ) if err != nil { return Result{Error: err}, runInfo @@ -82,18 +86,32 @@ func (t *EstimateGasLimitTask) Run(ctx context.Context, lggr logger.Logger, vars maximumGasLimit := SelectGasLimit(chain.Config().EVM().GasEstimator(), t.jobType, t.specGasLimit) to := common.Address(toAddr) - gasLimit, err := chain.Client().EstimateGas(ctx, ethereum.CallMsg{ - From: common.Address(fromAddr), - To: &to, - Data: data, - }) + var gasLimit hexutil.Uint64 + args := map[string]interface{}{ + "from": common.Address(fromAddr), + "to": &to, + "input": hexutil.Bytes([]byte(data)), + } + + selectedBlock, err := selectBlock(string(block)) + if err != nil { + return Result{Error: err}, runInfo + } + err = chain.Client().CallContext(ctx, + &gasLimit, + "eth_estimateGas", + args, + selectedBlock, + ) + if err != nil { // Fallback to the maximum conceivable gas limit // if we're unable to call estimate gas for whatever reason. lggr.Warnw("EstimateGas: unable to estimate, fallback to configured limit", "err", err, "fallback", maximumGasLimit) return Result{Value: maximumGasLimit}, runInfo } - gasLimitDecimal, err := decimal.NewFromString(strconv.FormatUint(gasLimit, 10)) + + gasLimitDecimal, err := decimal.NewFromString(strconv.FormatUint(uint64(gasLimit), 10)) if err != nil { return Result{Error: err}, retryableRunInfo() } diff --git a/core/services/pipeline/task.eth_call.go b/core/services/pipeline/task.eth_call.go index 56b2df08c4e..3007f1d4bd6 100644 --- a/core/services/pipeline/task.eth_call.go +++ b/core/services/pipeline/task.eth_call.go @@ -5,8 +5,8 @@ import ( "fmt" "time" - "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" @@ -33,6 +33,7 @@ type ETHCallTask struct { GasUnlimited string `json:"gasUnlimited"` ExtractRevertReason bool `json:"extractRevertReason"` EVMChainID string `json:"evmChainID" mapstructure:"evmChainID"` + Block string `json:"block"` specGasLimit *uint32 legacyChains legacyevm.LegacyChainContainer @@ -78,6 +79,7 @@ func (t *ETHCallTask) Run(ctx context.Context, lggr logger.Logger, vars Vars, in gasFeeCap MaybeBigIntParam gasUnlimited BoolParam chainID StringParam + block StringParam ) err = multierr.Combine( errors.Wrap(ResolveParam(&contractAddr, From(VarExpr(t.Contract, vars), NonemptyString(t.Contract))), "contract"), @@ -89,6 +91,7 @@ func (t *ETHCallTask) Run(ctx context.Context, lggr logger.Logger, vars Vars, in errors.Wrap(ResolveParam(&gasFeeCap, From(VarExpr(t.GasFeeCap, vars), t.GasFeeCap)), "gasFeeCap"), errors.Wrap(ResolveParam(&chainID, From(VarExpr(t.getEvmChainID(), vars), NonemptyString(t.getEvmChainID()), "")), "evmChainID"), errors.Wrap(ResolveParam(&gasUnlimited, From(VarExpr(t.GasUnlimited, vars), NonemptyString(t.GasUnlimited), false)), "gasUnlimited"), + errors.Wrap(ResolveParam(&block, From(VarExpr(t.Block, vars), t.Block)), "block"), ) if err != nil { return Result{Error: err}, runInfo @@ -115,23 +118,29 @@ func (t *ETHCallTask) Run(ctx context.Context, lggr logger.Logger, vars Vars, in } } - call := ethereum.CallMsg{ - To: (*common.Address)(&contractAddr), - From: (common.Address)(from), - Data: []byte(data), - Gas: uint64(selectedGas), - GasPrice: gasPrice.BigInt(), - GasTipCap: gasTipCap.BigInt(), - GasFeeCap: gasFeeCap.BigInt(), + args := map[string]interface{}{ + "from": common.Address(from), + "to": (*common.Address)(&contractAddr), + "input": hexutil.Bytes([]byte(data)), + "gas": hexutil.Uint64(uint64(selectedGas)), + "gasPrice": (*hexutil.Big)(gasPrice.BigInt()), + "maxFeePerGas": (*hexutil.Big)(gasFeeCap.BigInt()), + "maxPriorityFeePerGas": (*hexutil.Big)(gasTipCap.BigInt()), } - lggr = lggr.With("gas", call.Gas). - With("gasPrice", call.GasPrice). - With("gasTipCap", call.GasTipCap). - With("gasFeeCap", call.GasFeeCap) + selectedBlock, err := selectBlock(string(block)) + if err != nil { + return Result{Error: err}, runInfo + } + + lggr = lggr.With("gas", uint64(selectedGas)). + With("gasPrice", gasPrice.BigInt()). + With("gasTipCap", gasTipCap.BigInt()). + With("gasFeeCap", gasFeeCap.BigInt()) start := time.Now() - resp, err := chain.Client().CallContract(ctx, call, nil) + var resp hexutil.Bytes + err = chain.Client().CallContext(ctx, &resp, "eth_call", args, selectedBlock) elapsed := time.Since(start) if err != nil { if t.ExtractRevertReason { @@ -149,6 +158,5 @@ func (t *ETHCallTask) Run(ctx context.Context, lggr logger.Logger, vars Vars, in } promETHCallTime.WithLabelValues(t.DotID()).Set(float64(elapsed)) - - return Result{Value: resp}, runInfo + return Result{Value: []byte(resp)}, runInfo } diff --git a/core/services/pipeline/task.eth_call_test.go b/core/services/pipeline/task.eth_call_test.go index cb58a03a9df..e814ee8d07a 100644 --- a/core/services/pipeline/task.eth_call_test.go +++ b/core/services/pipeline/task.eth_call_test.go @@ -6,6 +6,7 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -43,6 +44,7 @@ func TestETHCallTask(t *testing.T) { data string evmChainID string gas string + block string specGasLimit *uint32 vars pipeline.Vars inputs []pipeline.Result @@ -52,24 +54,43 @@ func TestETHCallTask(t *testing.T) { expectedErrorContains string }{ { - "happy with empty from", - "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", - "", - "$(foo)", - "0", - "", - nil, - pipeline.NewVarsFrom(map[string]interface{}{ + "happy with empty from", // name + "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", // contract + "", // from + "$(foo)", // data + "0", // evmChainID + "", // gas + "", // block + nil, // specGasLimit + pipeline.NewVarsFrom(map[string]interface{}{ // vars "foo": []byte("foo bar"), }), - nil, - func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) { + nil, // inputs + func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) { // setupClientMocks contractAddr := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") + //[]byte("baz quux") ethClient. - On("CallContract", mock.Anything, ethereum.CallMsg{To: &contractAddr, Gas: uint64(drJobTypeGasLimit), Data: []byte("foo bar")}, (*big.Int)(nil)). - Return([]byte("baz quux"), nil) + On("CallContext", + mock.Anything, + mock.Anything, + "eth_call", + map[string]interface{}{ + "from": common.Address{}, + "to": &contractAddr, + "gas": hexutil.Uint64(uint64(drJobTypeGasLimit)), + "input": hexutil.Bytes([]byte("foo bar")), + "gasPrice": (*hexutil.Big)(nil), + "maxFeePerGas": (*hexutil.Big)(nil), + "maxPriorityFeePerGas": (*hexutil.Big)(nil), + }, + "latest").Return(nil).Run(func(args mock.Arguments) { + resp := args.Get(1).(*hexutil.Bytes) + *resp = []byte("baz quux") + }) }, - []byte("baz quux"), nil, "", + []byte("baz quux"), // expected + nil, // expecedErrorCause + "", // expectedErrorContains }, { "happy with gas limit per task", @@ -78,19 +99,38 @@ func TestETHCallTask(t *testing.T) { "$(foo)", "0", "$(gasLimit)", + "", // block nil, pipeline.NewVarsFrom(map[string]interface{}{ "foo": []byte("foo bar"), "gasLimit": 100_000, }), nil, - func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) { + func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) { // setupClientMocks contractAddr := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") + //[]byte("baz quux") ethClient. - On("CallContract", mock.Anything, ethereum.CallMsg{To: &contractAddr, Gas: 100_000, Data: []byte("foo bar")}, (*big.Int)(nil)). - Return([]byte("baz quux"), nil) + On("CallContext", + mock.Anything, + mock.Anything, + "eth_call", + map[string]interface{}{ + "from": common.Address{}, + "to": &contractAddr, + "gas": hexutil.Uint64(uint64(100_000)), + "input": hexutil.Bytes([]byte("foo bar")), + "gasPrice": (*hexutil.Big)(nil), + "maxFeePerGas": (*hexutil.Big)(nil), + "maxPriorityFeePerGas": (*hexutil.Big)(nil), + }, + "latest").Return(nil).Run(func(args mock.Arguments) { + resp := args.Get(1).(*hexutil.Bytes) + *resp = []byte("baz quux") + }) }, - []byte("baz quux"), nil, "", + []byte("baz quux"), + nil, + "", }, { "happy with gas limit per spec", @@ -99,18 +139,37 @@ func TestETHCallTask(t *testing.T) { "$(foo)", "0", "", + "pending", // block &specGasLimit, pipeline.NewVarsFrom(map[string]interface{}{ "foo": []byte("foo bar"), }), nil, - func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) { + func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) { // setupClientMocks contractAddr := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") + //[]byte("baz quux") ethClient. - On("CallContract", mock.Anything, ethereum.CallMsg{To: &contractAddr, Gas: uint64(specGasLimit), Data: []byte("foo bar")}, (*big.Int)(nil)). - Return([]byte("baz quux"), nil) + On("CallContext", + mock.Anything, + mock.Anything, + "eth_call", + map[string]interface{}{ + "from": common.Address{}, + "to": &contractAddr, + "gas": hexutil.Uint64(uint64(specGasLimit)), + "input": hexutil.Bytes([]byte("foo bar")), + "gasPrice": (*hexutil.Big)(nil), + "maxFeePerGas": (*hexutil.Big)(nil), + "maxPriorityFeePerGas": (*hexutil.Big)(nil), + }, + "pending").Return(nil).Run(func(args mock.Arguments) { + resp := args.Get(1).(*hexutil.Bytes) + *resp = []byte("baz quux") + }) }, - []byte("baz quux"), nil, "", + []byte("baz quux"), // expected + nil, + "", }, { "happy with from addr", @@ -119,17 +178,34 @@ func TestETHCallTask(t *testing.T) { "$(foo)", "0", "", + "pending", // block nil, pipeline.NewVarsFrom(map[string]interface{}{ "foo": []byte("foo bar"), }), nil, - func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) { + func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) { // setupClientMocks contractAddr := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") fromAddr := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") + //[]byte("baz quux") ethClient. - On("CallContract", mock.Anything, ethereum.CallMsg{To: &contractAddr, Gas: uint64(drJobTypeGasLimit), From: fromAddr, Data: []byte("foo bar")}, (*big.Int)(nil)). - Return([]byte("baz quux"), nil) + On("CallContext", + mock.Anything, + mock.Anything, + "eth_call", + map[string]interface{}{ + "from": fromAddr, + "to": &contractAddr, + "gas": hexutil.Uint64(drJobTypeGasLimit), + "input": hexutil.Bytes([]byte("foo bar")), + "gasPrice": (*hexutil.Big)(nil), + "maxFeePerGas": (*hexutil.Big)(nil), + "maxPriorityFeePerGas": (*hexutil.Big)(nil), + }, + "pending").Return(nil).Run(func(args mock.Arguments) { + resp := args.Get(1).(*hexutil.Bytes) + *resp = []byte("baz quux") + }) }, []byte("baz quux"), nil, "", }, @@ -140,6 +216,7 @@ func TestETHCallTask(t *testing.T) { "$(foo)", "0", "", + "", nil, pipeline.NewVarsFrom(map[string]interface{}{ "foo": []byte("foo bar"), @@ -155,6 +232,7 @@ func TestETHCallTask(t *testing.T) { "$(foo)", "0", "", + "", nil, pipeline.NewVarsFrom(map[string]interface{}{ "foo": []byte("foo bar"), @@ -170,6 +248,7 @@ func TestETHCallTask(t *testing.T) { "$(foo)", "0", "", + "", nil, pipeline.NewVarsFrom(map[string]interface{}{ "zork": []byte("foo bar"), @@ -185,6 +264,7 @@ func TestETHCallTask(t *testing.T) { "$(foo)", "0", "", + "", nil, pipeline.NewVarsFrom(map[string]interface{}{ "foo": []byte(nil), @@ -200,6 +280,7 @@ func TestETHCallTask(t *testing.T) { "$(foo)", "0", "", + "", nil, pipeline.NewVarsFrom(map[string]interface{}{ "foo": []byte("foo bar"), @@ -208,6 +289,24 @@ func TestETHCallTask(t *testing.T) { func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) {}, nil, pipeline.ErrTooManyErrors, "task inputs", }, + { + "invalid block", // name + "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", // contract + "", // from + "$(foo)", // data + "0", // evmChainID + "", // gas + "invalidblock", // block + nil, // specGasLimit + pipeline.NewVarsFrom(map[string]interface{}{ // vars + "foo": []byte("foo bar"), + }), + nil, // inputs + func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) {}, // setupClientMocks + nil, // expected + nil, // expectedErrorCause + "unsupported block param", // expectedErrorContains + }, { "missing chainID", "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", @@ -215,6 +314,7 @@ func TestETHCallTask(t *testing.T) { "$(foo)", "$(evmChainID)", "", + "", nil, pipeline.NewVarsFrom(map[string]interface{}{ "foo": []byte("foo bar"), @@ -241,6 +341,7 @@ func TestETHCallTask(t *testing.T) { Data: test.data, EVMChainID: test.evmChainID, Gas: test.gas, + Block: test.block, } ethClient := evmclimocks.NewClient(t) diff --git a/core/services/pipeline/task.vrfv2plus.go b/core/services/pipeline/task.vrfv2plus.go index ff9f96e7eca..6bc299eeb6f 100644 --- a/core/services/pipeline/task.vrfv2plus.go +++ b/core/services/pipeline/task.vrfv2plus.go @@ -138,6 +138,7 @@ func (t *VRFTaskV2Plus) Run(_ context.Context, lggr logger.Logger, vars Vars, in return Result{Error: err}, retryableRunInfo() } // onlyPremium is false because this task assumes that chainlink node fulfills the VRF request + // gas cost should be billed to the requesting subscription b, err := vrfCoordinatorV2PlusABI.Pack("fulfillRandomWords", onChainProof, rc, false /* onlyPremium */) if err != nil { return Result{Error: err}, runInfo diff --git a/core/services/vrf/vrfcommon/validate.go b/core/services/vrf/vrfcommon/validate.go index 07e8676ddb7..09f2e669483 100644 --- a/core/services/vrf/vrfcommon/validate.go +++ b/core/services/vrf/vrfcommon/validate.go @@ -47,9 +47,6 @@ func ValidatedVRFSpec(tomlString string) (job.Job, error) { if bytes.Equal(spec.PublicKey[:], empty[:]) { return jb, errors.Wrap(ErrKeyNotSet, "publicKey") } - if spec.MinIncomingConfirmations == 0 { - return jb, errors.Wrap(ErrKeyNotSet, "minIncomingConfirmations") - } if spec.CoordinatorAddress.String() == "" { return jb, errors.Wrap(ErrKeyNotSet, "coordinatorAddress") } diff --git a/core/testdata/testspecs/v2_specs.go b/core/testdata/testspecs/v2_specs.go index a78f5d8530c..5ca79f1abb4 100644 --- a/core/testdata/testspecs/v2_specs.go +++ b/core/testdata/testspecs/v2_specs.go @@ -376,6 +376,7 @@ estimate_gas [type=estimategaslimit to="%s" multiplier="1.1" data="$(generate_proof.output)" + block="latest" ] simulate_fulfillment [type=ethcall to="%s" @@ -384,6 +385,7 @@ simulate_fulfillment [type=ethcall extractRevertReason=true contract="%s" data="$(generate_proof.output)" + block="latest" ] decode_log->generate_proof->estimate_gas->simulate_fulfillment `, coordinatorAddress, coordinatorAddress, coordinatorAddress) diff --git a/integration-tests/client/chainlink_models.go b/integration-tests/client/chainlink_models.go index 665ff3c7465..2eba4c42716 100644 --- a/integration-tests/client/chainlink_models.go +++ b/integration-tests/client/chainlink_models.go @@ -654,7 +654,8 @@ generate_proof [type=vrfv2plus estimate_gas [type=estimategaslimit to="{{ .Address }}" multiplier="{{ .EstimateGasMultiplier }}" - data="$(generate_proof.output)"] + data="$(generate_proof.output)" + request="pending"] simulate_fulfillment [type=ethcall from="{{ .FromAddress }}" to="{{ .Address }}" @@ -662,7 +663,8 @@ simulate_fulfillment [type=ethcall gasPrice="$(jobSpec.maxGasPrice)" extractRevertReason=true contract="{{ .Address }}" - data="$(generate_proof.output)"] + data="$(generate_proof.output)" + request="pending"] decode_log->generate_proof->estimate_gas->simulate_fulfillment` return MarshallTemplate(d, "VRFV2 Plus pipeline template", sourceString) } From 8a49b855f2e6c44062bc350f70ccef758f9d034b Mon Sep 17 00:00:00 2001 From: jinhoonbang Date: Thu, 8 Feb 2024 20:30:47 -0800 Subject: [PATCH 02/10] fix script build error --- core/scripts/common/vrf/setup-envs/main.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/scripts/common/vrf/setup-envs/main.go b/core/scripts/common/vrf/setup-envs/main.go index cd23328b3d5..644ebe3d021 100644 --- a/core/scripts/common/vrf/setup-envs/main.go +++ b/core/scripts/common/vrf/setup-envs/main.go @@ -83,6 +83,7 @@ func main() { subscriptionBalanceNativeWeiString := flag.String("subscription-balance-native", constants.SubscriptionBalanceNativeWei, "amount to fund subscription with native token (Wei)") minConfs := flag.Int("min-confs", constants.MinConfs, "minimum confirmations") + nativeOnly := flag.Bool("native-only", false, "if true, link and link feed are not set up. Only used in v2 plus") linkAddress := flag.String("link-address", "", "address of link token") linkEthAddress := flag.String("link-eth-feed", "", "address of link eth feed") bhsContractAddressString := flag.String("bhs-address", "", "address of BHS contract") @@ -257,6 +258,8 @@ func main() { vrfKeyRegistrationConfig, contractAddresses, coordinatorConfigV2Plus, + *batchFulfillmentEnabled, + *nativeOnly, nodesMap, uint64(*maxGasPriceGwei), coordinatorJobSpecConfig, From 16bd77e93e9d490e0fd8b7706e7dc858afe8a6d5 Mon Sep 17 00:00:00 2001 From: jinhoonbang Date: Sat, 10 Feb 2024 08:47:17 -0500 Subject: [PATCH 03/10] fix failing automation test --- .../internal/cltest/contract_mock_receiver.go | 57 +++++++++++++------ core/services/keeper/upkeep_executer_test.go | 34 ++++++----- integration-tests/client/chainlink_models.go | 4 +- 3 files changed, 61 insertions(+), 34 deletions(-) diff --git a/core/internal/cltest/contract_mock_receiver.go b/core/internal/cltest/contract_mock_receiver.go index 2a0fc5a50f5..f1fb799d0a6 100644 --- a/core/internal/cltest/contract_mock_receiver.go +++ b/core/internal/cltest/contract_mock_receiver.go @@ -5,7 +5,6 @@ import ( "reflect" "testing" - "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -45,17 +44,25 @@ func (receiver contractMockReceiver) MockResponse(funcName string, responseArgs return receiver.ethMock. On( - "CallContract", + "CallContext", mock.Anything, - mock.MatchedBy(func(callArgs ethereum.CallMsg) bool { - return *callArgs.To == receiver.address && - hexutil.Encode(callArgs.Data)[0:funcSigLength] == funcSig + mock.Anything, + "eth_call", + mock.MatchedBy(func(args map[string]interface{}) bool { + to := args["to"].(*common.Address) + data := args["input"].(hexutil.Bytes) + return *to == receiver.address && + hexutil.Encode(data)[0:funcSigLength] == funcSig }), mock.Anything). - Return(encoded, nil) + Return(nil).Run(func(args mock.Arguments) { + resp := args.Get(1).(*hexutil.Bytes) + *resp = encoded + }) + } -func (receiver contractMockReceiver) MockMatchedResponse(funcName string, matcher func(callArgs ethereum.CallMsg) bool, responseArgs ...interface{}) *mock.Call { +func (receiver contractMockReceiver) MockMatchedResponse(funcName string, matcher func(args map[string]interface{}) bool, responseArgs ...interface{}) *mock.Call { funcSig := hexutil.Encode(receiver.abi.Methods[funcName].ID) if len(funcSig) != funcSigLength { receiver.t.Fatalf("Unable to find Registry contract function with name %s", funcName) @@ -63,17 +70,26 @@ func (receiver contractMockReceiver) MockMatchedResponse(funcName string, matche encoded := receiver.mustEncodeResponse(funcName, responseArgs...) + // TODO: ALL CALLER MATCHER FUNCTIONS SHOULD BE CHANGED + return receiver.ethMock. On( - "CallContract", + "CallContext", mock.Anything, - mock.MatchedBy(func(callArgs ethereum.CallMsg) bool { - return *callArgs.To == receiver.address && - hexutil.Encode(callArgs.Data)[0:funcSigLength] == funcSig && - matcher(callArgs) + mock.Anything, + "eth_call", + mock.MatchedBy(func(args map[string]interface{}) bool { + to := args["to"].(*common.Address) + data := args["input"].(hexutil.Bytes) + return *to == receiver.address && + hexutil.Encode(data)[0:funcSigLength] == funcSig && + matcher(args) }), mock.Anything). - Return(encoded, nil) + Return(nil).Run(func(args mock.Arguments) { + resp := args.Get(1).(*hexutil.Bytes) + *resp = encoded + }) } func (receiver contractMockReceiver) MockRevertResponse(funcName string) *mock.Call { @@ -84,14 +100,19 @@ func (receiver contractMockReceiver) MockRevertResponse(funcName string) *mock.C return receiver.ethMock. On( - "CallContract", + "CallContext", + mock.Anything, mock.Anything, - mock.MatchedBy(func(callArgs ethereum.CallMsg) bool { - return *callArgs.To == receiver.address && - hexutil.Encode(callArgs.Data)[0:funcSigLength] == funcSig + "eth_call", + mock.MatchedBy(func(args map[string]interface{}) bool { + to := args["to"].(*common.Address) + data := args["input"].(hexutil.Bytes) + return *to == receiver.address && + hexutil.Encode(data)[0:funcSigLength] == funcSig }), mock.Anything). - Return(nil, errors.New("revert")) + Return(errors.New("revert")) + } func (receiver contractMockReceiver) mustEncodeResponse(funcName string, responseArgs ...interface{}) []byte { diff --git a/core/services/keeper/upkeep_executer_test.go b/core/services/keeper/upkeep_executer_test.go index 590c9720cb2..cef23b1e63d 100644 --- a/core/services/keeper/upkeep_executer_test.go +++ b/core/services/keeper/upkeep_executer_test.go @@ -6,8 +6,8 @@ import ( "testing" "time" - "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/onsi/gomega" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -148,15 +148,17 @@ func Test_UpkeepExecuter_PerformsUpkeep_Happy(t *testing.T) { registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_1ABI, registry.ContractAddress.Address()) registryMock.MockMatchedResponse( "checkUpkeep", - func(callArgs ethereum.CallMsg) bool { - return callArgs.GasPrice == nil && - callArgs.Gas == 0 + func(args map[string]interface{}) bool { + gasPrice := args["gasPrice"].(*hexutil.Big) + gas := args["gas"].(hexutil.Uint64) + return gasPrice == nil && + gas == 0 }, checkUpkeepResponse, ) registryMock.MockMatchedResponse( "performUpkeep", - func(callArgs ethereum.CallMsg) bool { return true }, + func(args map[string]interface{}) bool { return true }, checkPerformResponse, ) @@ -193,15 +195,17 @@ func Test_UpkeepExecuter_PerformsUpkeep_Happy(t *testing.T) { registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_1ABI, registry.ContractAddress.Address()) registryMock.MockMatchedResponse( "checkUpkeep", - func(callArgs ethereum.CallMsg) bool { - return callArgs.GasPrice == nil && - callArgs.Gas == 0 + func(args map[string]interface{}) bool { + gasPrice := args["gasPrice"].(*hexutil.Big) + gas := args["gas"].(hexutil.Uint64) + return gasPrice == nil && + gas == 0 }, checkUpkeepResponse, ) registryMock.MockMatchedResponse( "performUpkeep", - func(callArgs ethereum.CallMsg) bool { return true }, + func(args map[string]interface{}) bool { return true }, checkPerformResponse, ) @@ -239,15 +243,17 @@ func Test_UpkeepExecuter_PerformsUpkeep_Happy(t *testing.T) { registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_1ABI, registry.ContractAddress.Address()) registryMock.MockMatchedResponse( "checkUpkeep", - func(callArgs ethereum.CallMsg) bool { - return callArgs.GasPrice == nil && - callArgs.Gas == 0 + func(args map[string]interface{}) bool { + gasPrice := args["gasPrice"].(*hexutil.Big) + gas := args["gas"].(hexutil.Uint64) + return gasPrice == nil && + gas == 0 }, checkUpkeepResponse, ) registryMock.MockMatchedResponse( "performUpkeep", - func(callArgs ethereum.CallMsg) bool { return true }, + func(args map[string]interface{}) bool { return true }, checkPerformResponse, ) @@ -299,7 +305,7 @@ func Test_UpkeepExecuter_PerformsUpkeep_Happy(t *testing.T) { registryMock.MockResponse("checkUpkeep", checkUpkeepResponse) registryMock.MockMatchedResponse( "performUpkeep", - func(callArgs ethereum.CallMsg) bool { return true }, + func(args map[string]interface{}) bool { return true }, checkPerformResponse, ) // turn falls somewhere between 20-39 (blockCountPerTurn=20) diff --git a/integration-tests/client/chainlink_models.go b/integration-tests/client/chainlink_models.go index 40b029b94f8..a9e24054531 100644 --- a/integration-tests/client/chainlink_models.go +++ b/integration-tests/client/chainlink_models.go @@ -655,7 +655,7 @@ estimate_gas [type=estimategaslimit to="{{ .Address }}" multiplier="{{ .EstimateGasMultiplier }}" data="$(generate_proof.output)" - request="pending"] + block="pending"] simulate_fulfillment [type=ethcall from="{{ .FromAddress }}" to="{{ .Address }}" @@ -664,7 +664,7 @@ simulate_fulfillment [type=ethcall extractRevertReason=true contract="{{ .Address }}" data="$(generate_proof.output)" - request="pending"] + block="pending"] decode_log->generate_proof->estimate_gas->simulate_fulfillment` return MarshallTemplate(d, "VRFV2 Plus pipeline template", sourceString) } From f2c7c1fb8bfa2d13fc85cd45f38cf06de9634110 Mon Sep 17 00:00:00 2001 From: jinhoonbang Date: Sat, 10 Feb 2024 09:54:47 -0500 Subject: [PATCH 04/10] fix more tests --- .../internal/cltest/contract_mock_receiver.go | 27 ++++++++++++++++--- .../keeper/registry1_1_synchronizer_test.go | 22 +++++++-------- .../keeper/registry1_2_synchronizer_test.go | 18 ++++++------- .../keeper/registry1_3_synchronizer_test.go | 20 +++++++------- .../registry_synchronizer_helper_test.go | 6 ++--- core/services/keeper/upkeep_executer_test.go | 18 ++++++------- 6 files changed, 66 insertions(+), 45 deletions(-) diff --git a/core/internal/cltest/contract_mock_receiver.go b/core/internal/cltest/contract_mock_receiver.go index f1fb799d0a6..285e8ab9051 100644 --- a/core/internal/cltest/contract_mock_receiver.go +++ b/core/internal/cltest/contract_mock_receiver.go @@ -5,6 +5,7 @@ import ( "reflect" "testing" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -34,7 +35,27 @@ type contractMockReceiver struct { address common.Address } -func (receiver contractMockReceiver) MockResponse(funcName string, responseArgs ...interface{}) *mock.Call { +func (receiver contractMockReceiver) MockCallContractResponse(funcName string, responseArgs ...interface{}) *mock.Call { + funcSig := hexutil.Encode(receiver.abi.Methods[funcName].ID) + if len(funcSig) != funcSigLength { + receiver.t.Fatalf("Unable to find Registry contract function with name %s", funcName) + } + + encoded := receiver.mustEncodeResponse(funcName, responseArgs...) + + return receiver.ethMock. + On( + "CallContract", + mock.Anything, + mock.MatchedBy(func(callArgs ethereum.CallMsg) bool { + return *callArgs.To == receiver.address && + hexutil.Encode(callArgs.Data)[0:funcSigLength] == funcSig + }), + mock.Anything). + Return(encoded, nil) +} + +func (receiver contractMockReceiver) MockCallContextResponse(funcName string, responseArgs ...interface{}) *mock.Call { funcSig := hexutil.Encode(receiver.abi.Methods[funcName].ID) if len(funcSig) != funcSigLength { receiver.t.Fatalf("Unable to find Registry contract function with name %s", funcName) @@ -62,7 +83,7 @@ func (receiver contractMockReceiver) MockResponse(funcName string, responseArgs } -func (receiver contractMockReceiver) MockMatchedResponse(funcName string, matcher func(args map[string]interface{}) bool, responseArgs ...interface{}) *mock.Call { +func (receiver contractMockReceiver) MockCallContextMatchedResponse(funcName string, matcher func(args map[string]interface{}) bool, responseArgs ...interface{}) *mock.Call { funcSig := hexutil.Encode(receiver.abi.Methods[funcName].ID) if len(funcSig) != funcSigLength { receiver.t.Fatalf("Unable to find Registry contract function with name %s", funcName) @@ -92,7 +113,7 @@ func (receiver contractMockReceiver) MockMatchedResponse(funcName string, matche }) } -func (receiver contractMockReceiver) MockRevertResponse(funcName string) *mock.Call { +func (receiver contractMockReceiver) MockCallContextRevertResponse(funcName string) *mock.Call { funcSig := hexutil.Encode(receiver.abi.Methods[funcName].ID) if len(funcSig) != funcSigLength { receiver.t.Fatalf("Unable to find Registry contract function with name %s", funcName) diff --git a/core/services/keeper/registry1_1_synchronizer_test.go b/core/services/keeper/registry1_1_synchronizer_test.go index a4f03d4d34a..4cc3f73dc3f 100644 --- a/core/services/keeper/registry1_1_synchronizer_test.go +++ b/core/services/keeper/registry1_1_synchronizer_test.go @@ -61,12 +61,12 @@ func mockRegistry1_1( ethMock.On("HeadByNumber", mock.Anything, (*big.Int)(nil)). Return(&evmtypes.Head{Number: 10}, nil) - registryMock.MockResponse("getConfig", config).Once() - registryMock.MockResponse("getKeeperList", keeperList).Once() - registryMock.MockResponse("getCanceledUpkeepList", cancelledUpkeeps).Once() - registryMock.MockResponse("getUpkeepCount", upkeepCount).Once() + registryMock.MockCallContractResponse("getConfig", config).Once() + registryMock.MockCallContractResponse("getKeeperList", keeperList).Once() + registryMock.MockCallContractResponse("getCanceledUpkeepList", cancelledUpkeeps).Once() + registryMock.MockCallContractResponse("getUpkeepCount", upkeepCount).Once() if timesGetUpkeepMock > 0 { - registryMock.MockResponse("getUpkeep", upkeepConfig).Times(timesGetUpkeepMock) + registryMock.MockCallContractResponse("getUpkeep", upkeepConfig).Times(timesGetUpkeepMock) } } @@ -79,7 +79,7 @@ func Test_LogListenerOpts1_1(t *testing.T) { contractAddress := j.KeeperSpec.ContractAddress.Address() registryMock := cltest.NewContractMockReceiver(t, ethClient, keeper.Registry1_1ABI, contractAddress) - registryMock.MockResponse("typeAndVersion", "KeeperRegistry 1.1.0").Once() + registryMock.MockCallContractResponse("typeAndVersion", "KeeperRegistry 1.1.0").Once() registryWrapper, err := keeper.NewRegistryWrapper(j.KeeperSpec.ContractAddress, ethClient) require.NoError(t, err) @@ -226,8 +226,8 @@ func Test_RegistrySynchronizer1_1_ConfigSetLog(t *testing.T) { registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_1ABI, contractAddress) newConfig := registryConfig1_1 newConfig.BlockCountPerTurn = big.NewInt(40) // change from default - registryMock.MockResponse("getKeeperList", []common.Address{fromAddress}).Once() - registryMock.MockResponse("getConfig", newConfig).Once() + registryMock.MockCallContractResponse("getKeeperList", []common.Address{fromAddress}).Once() + registryMock.MockCallContractResponse("getConfig", newConfig).Once() cfg := configtest.NewGeneralConfig(t, nil) head := cltest.MustInsertHead(t, db, cfg.Database(), 1) @@ -273,8 +273,8 @@ func Test_RegistrySynchronizer1_1_KeepersUpdatedLog(t *testing.T) { addresses := []common.Address{fromAddress, testutils.NewAddress()} // change from default registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_1ABI, contractAddress) - registryMock.MockResponse("getConfig", registryConfig1_1).Once() - registryMock.MockResponse("getKeeperList", addresses).Once() + registryMock.MockCallContractResponse("getConfig", registryConfig1_1).Once() + registryMock.MockCallContractResponse("getKeeperList", addresses).Once() cfg := configtest.NewGeneralConfig(t, nil) head := cltest.MustInsertHead(t, db, cfg.Database(), 1) @@ -355,7 +355,7 @@ func Test_RegistrySynchronizer1_1_UpkeepRegisteredLog(t *testing.T) { cltest.WaitForCount(t, db, "upkeep_registrations", 1) registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_1ABI, contractAddress) - registryMock.MockResponse("getUpkeep", upkeepConfig1_1).Once() + registryMock.MockCallContractResponse("getUpkeep", upkeepConfig1_1).Once() cfg := configtest.NewGeneralConfig(t, nil) head := cltest.MustInsertHead(t, db, cfg.Database(), 1) diff --git a/core/services/keeper/registry1_2_synchronizer_test.go b/core/services/keeper/registry1_2_synchronizer_test.go index b7456ad94e4..60fd913221d 100644 --- a/core/services/keeper/registry1_2_synchronizer_test.go +++ b/core/services/keeper/registry1_2_synchronizer_test.go @@ -83,13 +83,13 @@ func mockRegistry1_2( ethMock.On("HeadByNumber", mock.Anything, (*big.Int)(nil)). Return(&evmtypes.Head{Number: 10}, nil) if getStateTime > 0 { - registryMock.MockResponse("getState", getState).Times(getStateTime) + registryMock.MockCallContractResponse("getState", getState).Times(getStateTime) } if getActiveUpkeepIDsTime > 0 { - registryMock.MockResponse("getActiveUpkeepIDs", activeUpkeepIDs).Times(getActiveUpkeepIDsTime) + registryMock.MockCallContractResponse("getActiveUpkeepIDs", activeUpkeepIDs).Times(getActiveUpkeepIDsTime) } if timesGetUpkeepMock > 0 { - registryMock.MockResponse("getUpkeep", upkeepConfig).Times(timesGetUpkeepMock) + registryMock.MockCallContractResponse("getUpkeep", upkeepConfig).Times(timesGetUpkeepMock) } } @@ -102,7 +102,7 @@ func Test_LogListenerOpts1_2(t *testing.T) { contractAddress := j.KeeperSpec.ContractAddress.Address() registryMock := cltest.NewContractMockReceiver(t, ethClient, keeper.Registry1_1ABI, contractAddress) - registryMock.MockResponse("typeAndVersion", "KeeperRegistry 1.2.0").Once() + registryMock.MockCallContractResponse("typeAndVersion", "KeeperRegistry 1.2.0").Once() registryWrapper, err := keeper.NewRegistryWrapper(j.KeeperSpec.ContractAddress, ethClient) require.NoError(t, err) @@ -246,7 +246,7 @@ func Test_RegistrySynchronizer1_2_ConfigSetLog(t *testing.T) { registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_2ABI, contractAddress) newConfig := registryConfig1_2 newConfig.BlockCountPerTurn = big.NewInt(40) // change from default - registryMock.MockResponse("getState", registry1_2.GetState{ + registryMock.MockCallContractResponse("getState", registry1_2.GetState{ State: registryState1_2, Config: newConfig, Keepers: []common.Address{fromAddress}, @@ -297,7 +297,7 @@ func Test_RegistrySynchronizer1_2_KeepersUpdatedLog(t *testing.T) { addresses := []common.Address{fromAddress, testutils.NewAddress()} // change from default registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_2ABI, contractAddress) - registryMock.MockResponse("getState", registry1_2.GetState{ + registryMock.MockCallContractResponse("getState", registry1_2.GetState{ State: registryState1_2, Config: registryConfig1_2, Keepers: addresses, @@ -385,7 +385,7 @@ func Test_RegistrySynchronizer1_2_UpkeepRegisteredLog(t *testing.T) { cltest.WaitForCount(t, db, "upkeep_registrations", 1) registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_2ABI, contractAddress) - registryMock.MockResponse("getUpkeep", upkeepConfig1_2).Once() + registryMock.MockCallContractResponse("getUpkeep", upkeepConfig1_2).Once() cfg := configtest.NewGeneralConfig(t, nil) head := cltest.MustInsertHead(t, db, cfg.Database(), 1) @@ -493,7 +493,7 @@ func Test_RegistrySynchronizer1_2_UpkeepGasLimitSetLog(t *testing.T) { registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_2ABI, contractAddress) newConfig := upkeepConfig1_2 newConfig.ExecuteGas = 4_000_000 // change from default - registryMock.MockResponse("getUpkeep", newConfig).Once() + registryMock.MockCallContractResponse("getUpkeep", newConfig).Once() cfg := configtest.NewGeneralConfig(t, nil) head := cltest.MustInsertHead(t, db, cfg.Database(), 1) @@ -535,7 +535,7 @@ func Test_RegistrySynchronizer1_2_UpkeepReceivedLog(t *testing.T) { cltest.WaitForCount(t, db, "upkeep_registrations", 1) registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_2ABI, contractAddress) - registryMock.MockResponse("getUpkeep", upkeepConfig1_2).Once() + registryMock.MockCallContractResponse("getUpkeep", upkeepConfig1_2).Once() cfg := configtest.NewGeneralConfig(t, nil) head := cltest.MustInsertHead(t, db, cfg.Database(), 1) diff --git a/core/services/keeper/registry1_3_synchronizer_test.go b/core/services/keeper/registry1_3_synchronizer_test.go index 77bb873e1d0..031e305b83c 100644 --- a/core/services/keeper/registry1_3_synchronizer_test.go +++ b/core/services/keeper/registry1_3_synchronizer_test.go @@ -85,13 +85,13 @@ func mockRegistry1_3( ethMock.On("HeadByNumber", mock.Anything, (*big.Int)(nil)). Return(&evmtypes.Head{Number: 10}, nil) if getStateTime > 0 { - registryMock.MockResponse("getState", getState).Times(getStateTime) + registryMock.MockCallContractResponse("getState", getState).Times(getStateTime) } if getActiveUpkeepIDsTime > 0 { - registryMock.MockResponse("getActiveUpkeepIDs", activeUpkeepIDs).Times(getActiveUpkeepIDsTime) + registryMock.MockCallContractResponse("getActiveUpkeepIDs", activeUpkeepIDs).Times(getActiveUpkeepIDsTime) } if timesGetUpkeepMock > 0 { - registryMock.MockResponse("getUpkeep", upkeepConfig).Times(timesGetUpkeepMock) + registryMock.MockCallContractResponse("getUpkeep", upkeepConfig).Times(timesGetUpkeepMock) } } @@ -104,7 +104,7 @@ func Test_LogListenerOpts1_3(t *testing.T) { contractAddress := j.KeeperSpec.ContractAddress.Address() registryMock := cltest.NewContractMockReceiver(t, ethClient, keeper.Registry1_1ABI, contractAddress) - registryMock.MockResponse("typeAndVersion", "KeeperRegistry 1.3.0").Once() + registryMock.MockCallContractResponse("typeAndVersion", "KeeperRegistry 1.3.0").Once() registryWrapper, err := keeper.NewRegistryWrapper(j.KeeperSpec.ContractAddress, ethClient) require.NoError(t, err) @@ -251,7 +251,7 @@ func Test_RegistrySynchronizer1_3_ConfigSetLog(t *testing.T) { registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_3ABI, contractAddress) newConfig := registryConfig1_3 newConfig.BlockCountPerTurn = big.NewInt(40) // change from default - registryMock.MockResponse("getState", registry1_3.GetState{ + registryMock.MockCallContractResponse("getState", registry1_3.GetState{ State: registryState1_3, Config: newConfig, Keepers: []common.Address{fromAddress}, @@ -302,7 +302,7 @@ func Test_RegistrySynchronizer1_3_KeepersUpdatedLog(t *testing.T) { addresses := []common.Address{fromAddress, testutils.NewAddress()} // change from default registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_3ABI, contractAddress) - registryMock.MockResponse("getState", registry1_3.GetState{ + registryMock.MockCallContractResponse("getState", registry1_3.GetState{ State: registryState1_3, Config: registryConfig1_3, Keepers: addresses, @@ -390,7 +390,7 @@ func Test_RegistrySynchronizer1_3_UpkeepRegisteredLog(t *testing.T) { cltest.WaitForCount(t, db, "upkeep_registrations", 1) registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_3ABI, contractAddress) - registryMock.MockResponse("getUpkeep", upkeepConfig1_3).Once() + registryMock.MockCallContractResponse("getUpkeep", upkeepConfig1_3).Once() cfg := configtest.NewGeneralConfig(t, nil) head := cltest.MustInsertHead(t, db, cfg.Database(), 1) @@ -498,7 +498,7 @@ func Test_RegistrySynchronizer1_3_UpkeepGasLimitSetLog(t *testing.T) { registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_3ABI, contractAddress) newConfig := upkeepConfig1_3 newConfig.ExecuteGas = 4_000_000 // change from default - registryMock.MockResponse("getUpkeep", newConfig).Once() + registryMock.MockCallContractResponse("getUpkeep", newConfig).Once() cfg := configtest.NewGeneralConfig(t, nil) head := cltest.MustInsertHead(t, db, cfg.Database(), 1) @@ -540,7 +540,7 @@ func Test_RegistrySynchronizer1_3_UpkeepReceivedLog(t *testing.T) { cltest.WaitForCount(t, db, "upkeep_registrations", 1) registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_3ABI, contractAddress) - registryMock.MockResponse("getUpkeep", upkeepConfig1_3).Once() + registryMock.MockCallContractResponse("getUpkeep", upkeepConfig1_3).Once() cfg := configtest.NewGeneralConfig(t, nil) head := cltest.MustInsertHead(t, db, cfg.Database(), 1) @@ -699,7 +699,7 @@ func Test_RegistrySynchronizer1_3_UpkeepCheckDataUpdatedLog(t *testing.T) { registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_3ABI, contractAddress) newConfig := upkeepConfig1_3 newConfig.CheckData = newCheckData // changed from default - registryMock.MockResponse("getUpkeep", newConfig).Once() + registryMock.MockCallContractResponse("getUpkeep", newConfig).Once() updatedLog := registry1_3.KeeperRegistryUpkeepCheckDataUpdated{Id: upkeepId, NewCheckData: newCheckData} logBroadcast := logmocks.NewBroadcast(t) diff --git a/core/services/keeper/registry_synchronizer_helper_test.go b/core/services/keeper/registry_synchronizer_helper_test.go index 19ba2eedbbb..94545bc040b 100644 --- a/core/services/keeper/registry_synchronizer_helper_test.go +++ b/core/services/keeper/registry_synchronizer_helper_test.go @@ -54,13 +54,13 @@ func setupRegistrySync(t *testing.T, version keeper.RegistryVersion) ( switch version { case keeper.RegistryVersion_1_0, keeper.RegistryVersion_1_1: registryMock := cltest.NewContractMockReceiver(t, ethClient, keeper.Registry1_1ABI, contractAddress) - registryMock.MockResponse("typeAndVersion", "KeeperRegistry 1.1.1").Once() + registryMock.MockCallContractResponse("typeAndVersion", "KeeperRegistry 1.1.1").Once() case keeper.RegistryVersion_1_2: registryMock := cltest.NewContractMockReceiver(t, ethClient, keeper.Registry1_2ABI, contractAddress) - registryMock.MockResponse("typeAndVersion", "KeeperRegistry 1.2.0").Once() + registryMock.MockCallContractResponse("typeAndVersion", "KeeperRegistry 1.2.0").Once() case keeper.RegistryVersion_1_3: registryMock := cltest.NewContractMockReceiver(t, ethClient, keeper.Registry1_3ABI, contractAddress) - registryMock.MockResponse("typeAndVersion", "KeeperRegistry 1.3.0").Once() + registryMock.MockCallContractResponse("typeAndVersion", "KeeperRegistry 1.3.0").Once() case keeper.RegistryVersion_2_0, keeper.RegistryVersion_2_1: t.Fatalf("Unsupported version: %s", version) } diff --git a/core/services/keeper/upkeep_executer_test.go b/core/services/keeper/upkeep_executer_test.go index cef23b1e63d..6700a04c8d8 100644 --- a/core/services/keeper/upkeep_executer_test.go +++ b/core/services/keeper/upkeep_executer_test.go @@ -146,7 +146,7 @@ func Test_UpkeepExecuter_PerformsUpkeep_Happy(t *testing.T) { Run(func(mock.Arguments) { ethTxCreated.ItHappened() }) registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_1ABI, registry.ContractAddress.Address()) - registryMock.MockMatchedResponse( + registryMock.MockCallContextMatchedResponse( "checkUpkeep", func(args map[string]interface{}) bool { gasPrice := args["gasPrice"].(*hexutil.Big) @@ -156,7 +156,7 @@ func Test_UpkeepExecuter_PerformsUpkeep_Happy(t *testing.T) { }, checkUpkeepResponse, ) - registryMock.MockMatchedResponse( + registryMock.MockCallContextMatchedResponse( "performUpkeep", func(args map[string]interface{}) bool { return true }, checkPerformResponse, @@ -193,7 +193,7 @@ func Test_UpkeepExecuter_PerformsUpkeep_Happy(t *testing.T) { Run(func(mock.Arguments) { ethTxCreated.ItHappened() }) registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_1ABI, registry.ContractAddress.Address()) - registryMock.MockMatchedResponse( + registryMock.MockCallContextMatchedResponse( "checkUpkeep", func(args map[string]interface{}) bool { gasPrice := args["gasPrice"].(*hexutil.Big) @@ -203,7 +203,7 @@ func Test_UpkeepExecuter_PerformsUpkeep_Happy(t *testing.T) { }, checkUpkeepResponse, ) - registryMock.MockMatchedResponse( + registryMock.MockCallContextMatchedResponse( "performUpkeep", func(args map[string]interface{}) bool { return true }, checkPerformResponse, @@ -241,7 +241,7 @@ func Test_UpkeepExecuter_PerformsUpkeep_Happy(t *testing.T) { require.NoError(t, err) registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_1ABI, registry.ContractAddress.Address()) - registryMock.MockMatchedResponse( + registryMock.MockCallContextMatchedResponse( "checkUpkeep", func(args map[string]interface{}) bool { gasPrice := args["gasPrice"].(*hexutil.Big) @@ -251,7 +251,7 @@ func Test_UpkeepExecuter_PerformsUpkeep_Happy(t *testing.T) { }, checkUpkeepResponse, ) - registryMock.MockMatchedResponse( + registryMock.MockCallContextMatchedResponse( "performUpkeep", func(args map[string]interface{}) bool { return true }, checkPerformResponse, @@ -302,8 +302,8 @@ func Test_UpkeepExecuter_PerformsUpkeep_Happy(t *testing.T) { Run(func(mock.Arguments) { etxs[0].ItHappened() }) registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_1ABI, registry.ContractAddress.Address()) - registryMock.MockResponse("checkUpkeep", checkUpkeepResponse) - registryMock.MockMatchedResponse( + registryMock.MockCallContextResponse("checkUpkeep", checkUpkeepResponse) + registryMock.MockCallContextMatchedResponse( "performUpkeep", func(args map[string]interface{}) bool { return true }, checkPerformResponse, @@ -333,7 +333,7 @@ func Test_UpkeepExecuter_PerformsUpkeep_Error(t *testing.T) { var wasCalled atomic.Bool registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_1ABI, registry.ContractAddress.Address()) - registryMock.MockRevertResponse("checkUpkeep").Run(func(args mock.Arguments) { + registryMock.MockCallContextRevertResponse("checkUpkeep").Run(func(args mock.Arguments) { wasCalled.Store(true) }) From 28917f75d42c78dd6e64f0b264a1db00d78c7332 Mon Sep 17 00:00:00 2001 From: jinhoonbang Date: Mon, 12 Feb 2024 22:46:21 -0500 Subject: [PATCH 05/10] integraiton test wip --- .../actions/vrf/common/models.go | 1 + .../actions/vrf/vrfv2/vrfv2_steps.go | 1 + .../actions/vrf/vrfv2plus/vrfv2plus_steps.go | 1 + integration-tests/client/chainlink_models.go | 39 ++++++++++++++++--- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/integration-tests/actions/vrf/common/models.go b/integration-tests/actions/vrf/common/models.go index bb486bd598b..ab6ca034800 100644 --- a/integration-tests/actions/vrf/common/models.go +++ b/integration-tests/actions/vrf/common/models.go @@ -66,4 +66,5 @@ type VRFJobSpecConfig struct { PollPeriod time.Duration RequestTimeout time.Duration VRFOwnerConfig *VRFOwnerConfig + SimulationBlock *string } diff --git a/integration-tests/actions/vrf/vrfv2/vrfv2_steps.go b/integration-tests/actions/vrf/vrfv2/vrfv2_steps.go index 3813a970a2b..900731ea507 100644 --- a/integration-tests/actions/vrf/vrfv2/vrfv2_steps.go +++ b/integration-tests/actions/vrf/vrfv2/vrfv2_steps.go @@ -170,6 +170,7 @@ func CreateVRFV2Job( Address: vrfJobSpecConfig.CoordinatorAddress, EstimateGasMultiplier: vrfJobSpecConfig.EstimateGasMultiplier, FromAddress: vrfJobSpecConfig.FromAddresses[0], + SimulationBlock: vrfJobSpecConfig.SimulationBlock, } ost, err := os.String() if err != nil { diff --git a/integration-tests/actions/vrf/vrfv2plus/vrfv2plus_steps.go b/integration-tests/actions/vrf/vrfv2plus/vrfv2plus_steps.go index 23a778f229e..624d9dfe0d6 100644 --- a/integration-tests/actions/vrf/vrfv2plus/vrfv2plus_steps.go +++ b/integration-tests/actions/vrf/vrfv2plus/vrfv2plus_steps.go @@ -90,6 +90,7 @@ func CreateVRFV2PlusJob( Address: vrfJobSpecConfig.CoordinatorAddress, EstimateGasMultiplier: vrfJobSpecConfig.EstimateGasMultiplier, FromAddress: vrfJobSpecConfig.FromAddresses[0], + SimulationBlock: vrfJobSpecConfig.SimulationBlock, } ost, err := os.String() if err != nil { diff --git a/integration-tests/client/chainlink_models.go b/integration-tests/client/chainlink_models.go index a9e24054531..17be7518df0 100644 --- a/integration-tests/client/chainlink_models.go +++ b/integration-tests/client/chainlink_models.go @@ -627,11 +627,23 @@ func (d *PipelineSpec) String() (string, error) { return MarshallTemplate(d, "API call pipeline template", sourceString) } +func getOptionalSimBlock(simBlock *string) (string, error) { + optionalSimBlock := "" + if simBlock != nil { + if *simBlock != "latest" && *simBlock != "pending" { + return "", fmt.Errorf("invalid simulation block value: %s", *simBlock) + } + optionalSimBlock = fmt.Sprintf("block=\"%s\"", *simBlock) + } + return optionalSimBlock, nil +} + // VRFV2TxPipelineSpec VRFv2 request with tx callback type VRFV2PlusTxPipelineSpec struct { Address string EstimateGasMultiplier float64 FromAddress string + SimulationBlock *string // can be nil, "latest" or "pending". } // Type returns the type of the pipeline @@ -641,7 +653,11 @@ func (d *VRFV2PlusTxPipelineSpec) Type() string { // String representation of the pipeline func (d *VRFV2PlusTxPipelineSpec) String() (string, error) { - sourceString := ` + optionalSimBlock, err := getOptionalSimBlock(d.SimulationBlock) + if err != nil { + return "", err + } + sourceTemplate := ` decode_log [type=ethabidecodelog abi="RandomWordsRequested(bytes32 indexed keyHash,uint256 requestId,uint256 preSeed,uint256 indexed subId,uint16 minimumRequestConfirmations,uint32 callbackGasLimit,uint32 numWords,bytes extraArgs,address indexed sender)" data="$(jobRun.logData)" @@ -655,7 +671,7 @@ estimate_gas [type=estimategaslimit to="{{ .Address }}" multiplier="{{ .EstimateGasMultiplier }}" data="$(generate_proof.output)" - block="pending"] + %s] simulate_fulfillment [type=ethcall from="{{ .FromAddress }}" to="{{ .Address }}" @@ -664,8 +680,10 @@ simulate_fulfillment [type=ethcall extractRevertReason=true contract="{{ .Address }}" data="$(generate_proof.output)" - block="pending"] + %s] decode_log->generate_proof->estimate_gas->simulate_fulfillment` + + sourceString := fmt.Sprintf(sourceTemplate, optionalSimBlock, optionalSimBlock) return MarshallTemplate(d, "VRFV2 Plus pipeline template", sourceString) } @@ -674,6 +692,7 @@ type VRFV2TxPipelineSpec struct { Address string EstimateGasMultiplier float64 FromAddress string + SimulationBlock *string // can be nil, "latest" or "pending". } // Type returns the type of the pipeline @@ -683,7 +702,11 @@ func (d *VRFV2TxPipelineSpec) Type() string { // String representation of the pipeline func (d *VRFV2TxPipelineSpec) String() (string, error) { - sourceString := ` + optionalSimBlock, err := getOptionalSimBlock(d.SimulationBlock) + if err != nil { + return "", err + } + sourceTemplate := ` decode_log [type=ethabidecodelog abi="RandomWordsRequested(bytes32 indexed keyHash,uint256 requestId,uint256 preSeed,uint64 indexed subId,uint16 minimumRequestConfirmations,uint32 callbackGasLimit,uint32 numWords,address indexed sender)" data="$(jobRun.logData)" @@ -696,7 +719,8 @@ vrf [type=vrfv2 estimate_gas [type=estimategaslimit to="{{ .Address }}" multiplier="{{ .EstimateGasMultiplier }}" - data="$(vrf.output)"] + data="$(vrf.output)" + %s] simulate [type=ethcall from="{{ .FromAddress }}" to="{{ .Address }}" @@ -704,8 +728,11 @@ simulate [type=ethcall gasPrice="$(jobSpec.maxGasPrice)" extractRevertReason=true contract="{{ .Address }}" - data="$(vrf.output)"] + data="$(vrf.output)" + %s] decode_log->vrf->estimate_gas->simulate` + + sourceString := fmt.Sprintf(sourceTemplate, optionalSimBlock, optionalSimBlock) return MarshallTemplate(d, "VRFV2 pipeline template", sourceString) } From 2a6ae457798db66a36a961bc8fa14aee0f70b35e Mon Sep 17 00:00:00 2001 From: jinhoonbang Date: Tue, 13 Feb 2024 15:36:50 -0800 Subject: [PATCH 06/10] add integration tests for pending simulation block and zero confirmation delay (only v2 plus) and add simulation block option to superscript --- core/scripts/common/vrf/jobs/jobs.go | 12 ++- core/scripts/common/vrf/setup-envs/main.go | 7 ++ .../vrfv2/testnet/v2scripts/super_scripts.go | 11 +++ .../testnet/v2plusscripts/super_scripts.go | 11 +++ core/services/pipeline/task.eth_call_test.go | 4 - .../actions/vrf/vrfv2/vrfv2_steps.go | 1 + .../actions/vrf/vrfv2plus/vrfv2plus_steps.go | 1 + integration-tests/smoke/vrfv2_test.go | 86 ++++++++++++++++++ integration-tests/smoke/vrfv2plus_test.go | 89 +++++++++++++++++++ integration-tests/testconfig/vrfv2/config.go | 5 ++ 10 files changed, 219 insertions(+), 8 deletions(-) diff --git a/core/scripts/common/vrf/jobs/jobs.go b/core/scripts/common/vrf/jobs/jobs.go index 7e304f431be..16f892e75f6 100644 --- a/core/scripts/common/vrf/jobs/jobs.go +++ b/core/scripts/common/vrf/jobs/jobs.go @@ -27,7 +27,8 @@ vrf [type=vrfv2 estimate_gas [type=estimategaslimit to="%s" multiplier="%f" - data="$(vrf.output)"] + data="$(vrf.output)" + block=%s] simulate [type=ethcall from="%s" to="%s" @@ -35,7 +36,8 @@ simulate [type=ethcall gasPrice="$(jobSpec.maxGasPrice)" extractRevertReason=true contract="%s" - data="$(vrf.output)"] + data="$(vrf.output)" + block=%s] decode_log->vrf->estimate_gas->simulate """` @@ -66,7 +68,8 @@ generate_proof [type=vrfv2plus estimate_gas [type=estimategaslimit to="%s" multiplier="%f" - data="$(generate_proof.output)"] + data="$(generate_proof.output)" + block=%s] simulate_fulfillment [type=ethcall from="%s" to="%s" @@ -74,7 +77,8 @@ simulate_fulfillment [type=ethcall gasPrice="$(jobSpec.maxGasPrice)" extractRevertReason=true contract="%s" - data="$(generate_proof.output)"] + data="$(generate_proof.output)" + block=%s] decode_log->generate_proof->estimate_gas->simulate_fulfillment """ ` diff --git a/core/scripts/common/vrf/setup-envs/main.go b/core/scripts/common/vrf/setup-envs/main.go index 644ebe3d021..6d0f73c0f18 100644 --- a/core/scripts/common/vrf/setup-envs/main.go +++ b/core/scripts/common/vrf/setup-envs/main.go @@ -94,6 +94,7 @@ func main() { "from this address you can perform `coordinator.oracleWithdraw` to withdraw earned funds from rand request fulfilments") deployVRFOwner := flag.Bool("deploy-vrfv2-owner", true, "whether to deploy VRF owner contracts") useTestCoordinator := flag.Bool("use-test-coordinator", true, "whether to use test coordinator contract or use the normal one") + simulationBlock := flag.String("simulation-block", "pending", "simulation block can be 'pending' or 'latest'") e := helpers.SetupEnv(false) flag.Parse() @@ -104,6 +105,10 @@ func main() { } fmt.Println("Using VRF Version:", *vrfVersion) + if *simulationBlock != "pending" && *simulationBlock != "latest" { + helpers.PanicErr(fmt.Errorf("simulation block must be 'pending' or 'latest'")) + } + fundingAmount := decimal.RequireFromString(*nodeSendingKeyFundingAmount).BigInt() subscriptionBalanceJuels := decimal.RequireFromString(*subscriptionBalanceJuelsString).BigInt() subscriptionBalanceNativeWei := decimal.RequireFromString(*subscriptionBalanceNativeWeiString).BigInt() @@ -229,6 +234,7 @@ func main() { *deployVRFOwner, coordinatorJobSpecConfig, *useTestCoordinator, + *simulationBlock, ) case "v2plus": coordinatorConfigV2Plus := v2plusscripts.CoordinatorConfigV2Plus{ @@ -263,6 +269,7 @@ func main() { nodesMap, uint64(*maxGasPriceGwei), coordinatorJobSpecConfig, + *simulationBlock, ) } diff --git a/core/scripts/vrfv2/testnet/v2scripts/super_scripts.go b/core/scripts/vrfv2/testnet/v2scripts/super_scripts.go index 1397274656c..5cfc3f81ce1 100644 --- a/core/scripts/vrfv2/testnet/v2scripts/super_scripts.go +++ b/core/scripts/vrfv2/testnet/v2scripts/super_scripts.go @@ -60,6 +60,7 @@ func DeployUniverseViaCLI(e helpers.Environment) { deployVRFOwner := deployCmd.Bool("deploy-vrf-owner", true, "whether to deploy VRF owner contracts") useTestCoordinator := deployCmd.Bool("use-test-coordinator", true, "whether to use test coordinator") + simulationBlock := deployCmd.String("simulation-block", "pending", "simulation block can be 'pending' or 'latest'") // optional flags fallbackWeiPerUnitLinkString := deployCmd.String("fallback-wei-per-unit-link", constants.FallbackWeiPerUnitLink.String(), "fallback wei/link ratio") @@ -83,6 +84,10 @@ func DeployUniverseViaCLI(e helpers.Environment) { reqsForTier4 := deployCmd.Int64("reqs-for-tier-4", constants.ReqsForTier4, "requests for tier 4") reqsForTier5 := deployCmd.Int64("reqs-for-tier-5", constants.ReqsForTier5, "requests for tier 5") + if *simulationBlock != "pending" && *simulationBlock != "latest" { + helpers.PanicErr(fmt.Errorf("simulation block must be 'pending' or 'latest'")) + } + helpers.ParseArgs( deployCmd, os.Args[2:], ) @@ -162,6 +167,7 @@ func DeployUniverseViaCLI(e helpers.Environment) { *deployVRFOwner, coordinatorJobSpecConfig, *useTestCoordinator, + *simulationBlock, ) vrfPrimaryNode := nodesMap[model.VRFPrimaryNodeName] @@ -181,6 +187,7 @@ func VRFV2DeployUniverse( deployVRFOwner bool, coordinatorJobSpecConfig model.CoordinatorJobSpecConfig, useTestCoordinator bool, + simulationBlock string, ) model.JobSpecs { var compressedPkHex string var keyHash common.Hash @@ -347,6 +354,7 @@ func VRFV2DeployUniverse( coordinatorJobSpecConfig.RequestTimeout, //requestTimeout contractAddresses.CoordinatorAddress, coordinatorJobSpecConfig.EstimateGasMultiplier, //estimateGasMultiplier + simulationBlock, func() string { if keys := nodesMap[model.VRFPrimaryNodeName].SendingKeys; len(keys) > 0 { return keys[0].Address @@ -355,6 +363,7 @@ func VRFV2DeployUniverse( }(), contractAddresses.CoordinatorAddress, contractAddresses.CoordinatorAddress, + simulationBlock, ) if deployVRFOwner { formattedVrfPrimaryJobSpec = strings.Replace(formattedVrfPrimaryJobSpec, @@ -378,6 +387,7 @@ func VRFV2DeployUniverse( coordinatorJobSpecConfig.RequestTimeout, //requestTimeout contractAddresses.CoordinatorAddress, coordinatorJobSpecConfig.EstimateGasMultiplier, //estimateGasMultiplier + simulationBlock, func() string { if keys := nodesMap[model.VRFPrimaryNodeName].SendingKeys; len(keys) > 0 { return keys[0].Address @@ -386,6 +396,7 @@ func VRFV2DeployUniverse( }(), contractAddresses.CoordinatorAddress, contractAddresses.CoordinatorAddress, + simulationBlock, ) if deployVRFOwner { formattedVrfBackupJobSpec = strings.Replace(formattedVrfBackupJobSpec, diff --git a/core/scripts/vrfv2plus/testnet/v2plusscripts/super_scripts.go b/core/scripts/vrfv2plus/testnet/v2plusscripts/super_scripts.go index 327c959f90d..fcea01b71c8 100644 --- a/core/scripts/vrfv2plus/testnet/v2plusscripts/super_scripts.go +++ b/core/scripts/vrfv2plus/testnet/v2plusscripts/super_scripts.go @@ -487,6 +487,7 @@ func DeployUniverseViaCLI(e helpers.Environment) { estimateGasMultiplier := deployCmd.Float64("estimate-gas-multiplier", 1.1, "") pollPeriod := deployCmd.String("poll-period", "300ms", "") requestTimeout := deployCmd.String("request-timeout", "30m0s", "") + simulationBlock := deployCmd.String("simulation-block", "pending", "simulation block can be 'pending' or 'latest'") // optional flags fallbackWeiPerUnitLinkString := deployCmd.String("fallback-wei-per-unit-link", "6e16", "fallback wei/link ratio") @@ -517,6 +518,10 @@ func DeployUniverseViaCLI(e helpers.Environment) { } } + if *simulationBlock != "pending" && *simulationBlock != "latest" { + helpers.PanicErr(fmt.Errorf("simulation block must be 'pending' or 'latest'")) + } + fallbackWeiPerUnitLink := decimal.RequireFromString(*fallbackWeiPerUnitLinkString).BigInt() subscriptionBalanceJuels := decimal.RequireFromString(*subscriptionBalanceJuelsString).BigInt() subscriptionBalanceNativeWei := decimal.RequireFromString(*subscriptionBalanceNativeWeiString).BigInt() @@ -584,6 +589,7 @@ func DeployUniverseViaCLI(e helpers.Environment) { nodesMap, uint64(*gasLaneMaxGas), coordinatorJobSpecConfig, + *simulationBlock, ) vrfPrimaryNode := nodesMap[model.VRFPrimaryNodeName] @@ -604,6 +610,7 @@ func VRFV2PlusDeployUniverse(e helpers.Environment, nodesMap map[string]model.Node, gasLaneMaxGas uint64, coordinatorJobSpecConfig model.CoordinatorJobSpecConfig, + simulationBlock string, ) model.JobSpecs { var compressedPkHex string var keyHash common.Hash @@ -741,6 +748,7 @@ func VRFV2PlusDeployUniverse(e helpers.Environment, coordinatorJobSpecConfig.RequestTimeout, //requestTimeout contractAddresses.CoordinatorAddress, coordinatorJobSpecConfig.EstimateGasMultiplier, //estimateGasMultiplier + simulationBlock, func() string { if keys := nodesMap[model.VRFPrimaryNodeName].SendingKeys; len(keys) > 0 { return keys[0].Address @@ -749,6 +757,7 @@ func VRFV2PlusDeployUniverse(e helpers.Environment, }(), contractAddresses.CoordinatorAddress, contractAddresses.CoordinatorAddress, + simulationBlock, ) formattedVrfV2PlusBackupJobSpec := fmt.Sprintf( @@ -765,6 +774,7 @@ func VRFV2PlusDeployUniverse(e helpers.Environment, coordinatorJobSpecConfig.RequestTimeout, //requestTimeout contractAddresses.CoordinatorAddress, coordinatorJobSpecConfig.EstimateGasMultiplier, //estimateGasMultiplier + simulationBlock, func() string { if keys := nodesMap[model.VRFPrimaryNodeName].SendingKeys; len(keys) > 0 { return keys[0].Address @@ -773,6 +783,7 @@ func VRFV2PlusDeployUniverse(e helpers.Environment, }(), contractAddresses.CoordinatorAddress, contractAddresses.CoordinatorAddress, + simulationBlock, ) formattedBHSJobSpec := fmt.Sprintf( diff --git a/core/services/pipeline/task.eth_call_test.go b/core/services/pipeline/task.eth_call_test.go index e814ee8d07a..e91c1cbba84 100644 --- a/core/services/pipeline/task.eth_call_test.go +++ b/core/services/pipeline/task.eth_call_test.go @@ -68,7 +68,6 @@ func TestETHCallTask(t *testing.T) { nil, // inputs func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) { // setupClientMocks contractAddr := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") - //[]byte("baz quux") ethClient. On("CallContext", mock.Anything, @@ -108,7 +107,6 @@ func TestETHCallTask(t *testing.T) { nil, func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) { // setupClientMocks contractAddr := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") - //[]byte("baz quux") ethClient. On("CallContext", mock.Anything, @@ -147,7 +145,6 @@ func TestETHCallTask(t *testing.T) { nil, func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) { // setupClientMocks contractAddr := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") - //[]byte("baz quux") ethClient. On("CallContext", mock.Anything, @@ -187,7 +184,6 @@ func TestETHCallTask(t *testing.T) { func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) { // setupClientMocks contractAddr := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") fromAddr := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") - //[]byte("baz quux") ethClient. On("CallContext", mock.Anything, diff --git a/integration-tests/actions/vrf/vrfv2/vrfv2_steps.go b/integration-tests/actions/vrf/vrfv2/vrfv2_steps.go index 900731ea507..39d7133dd46 100644 --- a/integration-tests/actions/vrf/vrfv2/vrfv2_steps.go +++ b/integration-tests/actions/vrf/vrfv2/vrfv2_steps.go @@ -395,6 +395,7 @@ func setupVRFNode(contracts *vrfcommon.VRFContracts, chainID *big.Int, vrfv2Conf BatchFulfillmentGasMultiplier: *vrfv2Config.VRFJobBatchFulfillmentGasMultiplier, PollPeriod: vrfv2Config.VRFJobPollPeriod.Duration, RequestTimeout: vrfv2Config.VRFJobRequestTimeout.Duration, + SimulationBlock: vrfv2Config.VRFJobSimulationBlock, VRFOwnerConfig: vrfOwnerConfig, } diff --git a/integration-tests/actions/vrf/vrfv2plus/vrfv2plus_steps.go b/integration-tests/actions/vrf/vrfv2plus/vrfv2plus_steps.go index 624d9dfe0d6..a3c6352bf37 100644 --- a/integration-tests/actions/vrf/vrfv2plus/vrfv2plus_steps.go +++ b/integration-tests/actions/vrf/vrfv2plus/vrfv2plus_steps.go @@ -333,6 +333,7 @@ func setupVRFNode(contracts *vrfcommon.VRFContracts, chainID *big.Int, vrfv2Conf BatchFulfillmentGasMultiplier: *vrfv2Config.VRFJobBatchFulfillmentGasMultiplier, PollPeriod: vrfv2Config.VRFJobPollPeriod.Duration, RequestTimeout: vrfv2Config.VRFJobRequestTimeout.Duration, + SimulationBlock: vrfv2Config.VRFJobSimulationBlock, VRFOwnerConfig: nil, } diff --git a/integration-tests/smoke/vrfv2_test.go b/integration-tests/smoke/vrfv2_test.go index c289cd019c1..5d77bbd1a11 100644 --- a/integration-tests/smoke/vrfv2_test.go +++ b/integration-tests/smoke/vrfv2_test.go @@ -866,3 +866,89 @@ func TestVRFV2WithBHS(t *testing.T) { require.Equal(t, 0, randomWordsRequestedEvent.Raw.BlockHash.Cmp(randRequestBlockHash)) }) } + +func TestVRFV2PendingBlockSimulation(t *testing.T) { + t.Parallel() + l := logging.GetTestLogger(t) + + config, err := tc.GetConfig("Smoke", tc.VRFv2) + require.NoError(t, err, "Error getting config") + + config.VRFv2.General.VRFJobSimulationBlock = ptr.Ptr[string]("pending") + useVRFOwner := false + useTestCoordinator := false + network, err := actions.EthereumNetworkConfigFromConfig(l, &config) + require.NoError(t, err, "Error building ethereum network config") + + env, err := test_env.NewCLTestEnvBuilder(). + WithTestInstance(t). + WithTestConfig(&config). + WithPrivateEthereumNetwork(network). + WithCLNodes(1). + WithFunding(big.NewFloat(*config.Common.ChainlinkNodeFunding)). + WithStandardCleanup(). + Build() + require.NoError(t, err, "error creating test env") + + env.ParallelTransactions(true) + + mockETHLinkFeed, err := actions.DeployMockETHLinkFeed(env.ContractDeployer, big.NewInt(*config.VRFv2.General.LinkNativeFeedResponse)) + require.NoError(t, err) + linkToken, err := actions.DeployLINKToken(env.ContractDeployer) + require.NoError(t, err) + + // register proving key against oracle address (sending key) in order to test oracleWithdraw + defaultWalletAddress := env.EVMClient.GetDefaultWallet().Address() + + numberOfTxKeysToCreate := 1 + vrfv2Contracts, subIDs, vrfv2KeyData, nodesMap, err := vrfv2.SetupVRFV2Environment( + env, + []vrfcommon.VRFNodeType{vrfcommon.VRF}, + &config, + useVRFOwner, + useTestCoordinator, + linkToken, + mockETHLinkFeed, + defaultWalletAddress, + numberOfTxKeysToCreate, + 1, + 1, + l, + ) + require.NoError(t, err, "error setting up VRF v2 env") + + subID := subIDs[0] + + subscription, err := vrfv2Contracts.CoordinatorV2.GetSubscription(testcontext.Get(t), subID) + require.NoError(t, err, "error getting subscription information") + + vrfv2.LogSubDetails(l, subscription, subID, vrfv2Contracts.CoordinatorV2) + + jobRunsBeforeTest, err := nodesMap[vrfcommon.VRF].CLNode.API.MustReadRunsByJob(nodesMap[vrfcommon.VRF].Job.Data.ID) + require.NoError(t, err, "error reading job runs") + + // test and assert + randomWordsFulfilledEvent, err := vrfv2.RequestRandomnessAndWaitForFulfillment( + l, + vrfv2Contracts.VRFV2Consumer[0], + vrfv2Contracts.CoordinatorV2, + subID, + vrfv2KeyData, + *config.VRFv2.General.MinimumConfirmations, + *config.VRFv2.General.CallbackGasLimit, + *config.VRFv2.General.NumberOfWords, + *config.VRFv2.General.RandomnessRequestCountPerRequest, + *config.VRFv2.General.RandomnessRequestCountPerRequestDeviation, + config.VRFv2.General.RandomWordsFulfilledEventTimeout.Duration, + ) + require.NoError(t, err, "error requesting randomness and waiting for fulfilment") + + jobRuns, err := nodesMap[vrfcommon.VRF].CLNode.API.MustReadRunsByJob(nodesMap[vrfcommon.VRF].Job.Data.ID) + require.NoError(t, err, "error reading job runs") + require.Equal(t, len(jobRunsBeforeTest.Data)+1, len(jobRuns.Data)) + + status, err := vrfv2Contracts.VRFV2Consumer[0].GetRequestStatus(testcontext.Get(t), randomWordsFulfilledEvent.RequestId) + require.NoError(t, err, "error getting rand request status") + require.True(t, status.Fulfilled) + l.Debug().Bool("Fulfilment Status", status.Fulfilled).Msg("Random Words Request Fulfilment Status") +} diff --git a/integration-tests/smoke/vrfv2plus_test.go b/integration-tests/smoke/vrfv2plus_test.go index 701cae9a027..61b539e61c5 100644 --- a/integration-tests/smoke/vrfv2plus_test.go +++ b/integration-tests/smoke/vrfv2plus_test.go @@ -1140,3 +1140,92 @@ func TestVRFV2PlusWithBHS(t *testing.T) { require.Equal(t, 0, randomWordsRequestedEvent.Raw.BlockHash.Cmp(randRequestBlockHash)) }) } + +func TestVRFv2PlusPendingBlockSimulationAndZeroConfirmationDelays(t *testing.T) { + t.Parallel() + l := logging.GetTestLogger(t) + + config, err := tc.GetConfig("Smoke", tc.VRFv2Plus) + if err != nil { + t.Fatal(err) + } + + // override config with minConf = 0 and use pending block for simulation + config.VRFv2Plus.General.MinimumConfirmations = ptr.Ptr[uint16](0) + config.VRFv2Plus.General.VRFJobSimulationBlock = ptr.Ptr[string]("pending") + + network, err := actions.EthereumNetworkConfigFromConfig(l, &config) + require.NoError(t, err, "Error building ethereum network config") + + env, err := test_env.NewCLTestEnvBuilder(). + WithTestInstance(t). + WithTestConfig(&config). + WithPrivateEthereumNetwork(network). + WithCLNodes(1). + WithFunding(big.NewFloat(*config.Common.ChainlinkNodeFunding)). + WithStandardCleanup(). + Build() + require.NoError(t, err, "error creating test env") + + env.ParallelTransactions(true) + + mockETHLinkFeed, err := actions.DeployMockETHLinkFeed(env.ContractDeployer, big.NewInt(*config.VRFv2Plus.General.LinkNativeFeedResponse)) + require.NoError(t, err, "error deploying mock ETH/LINK feed") + + linkToken, err := actions.DeployLINKToken(env.ContractDeployer) + require.NoError(t, err, "error deploying LINK contract") + + numberOfTxKeysToCreate := 2 + vrfv2PlusContracts, subIDs, vrfv2PlusData, nodesMap, err := vrfv2plus.SetupVRFV2_5Environment( + env, + []vrfcommon.VRFNodeType{vrfcommon.VRF}, + &config, + linkToken, + mockETHLinkFeed, + numberOfTxKeysToCreate, + 1, + 1, + l, + ) + require.NoError(t, err, "error setting up VRF v2_5 env") + + subID := subIDs[0] + + subscription, err := vrfv2PlusContracts.CoordinatorV2Plus.GetSubscription(testcontext.Get(t), subID) + require.NoError(t, err, "error getting subscription information") + + vrfv2plus.LogSubDetails(l, subscription, subID, vrfv2PlusContracts.CoordinatorV2Plus) + + var isNativeBilling = false + + jobRunsBeforeTest, err := env.ClCluster.Nodes[0].API.MustReadRunsByJob(nodesMap[vrfcommon.VRF].Job.Data.ID) + require.NoError(t, err, "error reading job runs") + + l.Info().Uint16("minimumConfirmationDelay", *config.VRFv2Plus.General.MinimumConfirmations).Msg("Minimum Confirmation Delay") + + // test and assert + randomWordsFulfilledEvent, err := vrfv2plus.RequestRandomnessAndWaitForFulfillment( + vrfv2PlusContracts.VRFV2PlusConsumer[0], + vrfv2PlusContracts.CoordinatorV2Plus, + vrfv2PlusData, + subID, + isNativeBilling, + *config.VRFv2Plus.General.MinimumConfirmations, + *config.VRFv2Plus.General.CallbackGasLimit, + *config.VRFv2Plus.General.NumberOfWords, + *config.VRFv2Plus.General.RandomnessRequestCountPerRequest, + *config.VRFv2Plus.General.RandomnessRequestCountPerRequestDeviation, + config.VRFv2Plus.General.RandomWordsFulfilledEventTimeout.Duration, + l, + ) + require.NoError(t, err, "error requesting randomness and waiting for fulfilment") + + jobRuns, err := env.ClCluster.Nodes[0].API.MustReadRunsByJob(nodesMap[vrfcommon.VRF].Job.Data.ID) + require.NoError(t, err, "error reading job runs") + require.Equal(t, len(jobRunsBeforeTest.Data)+1, len(jobRuns.Data)) + + status, err := vrfv2PlusContracts.VRFV2PlusConsumer[0].GetRequestStatus(testcontext.Get(t), randomWordsFulfilledEvent.RequestId) + require.NoError(t, err, "error getting rand request status") + require.True(t, status.Fulfilled) + l.Debug().Bool("Fulfilment Status", status.Fulfilled).Msg("Random Words Request Fulfilment Status") +} diff --git a/integration-tests/testconfig/vrfv2/config.go b/integration-tests/testconfig/vrfv2/config.go index dca1319e8d8..c9037b59085 100644 --- a/integration-tests/testconfig/vrfv2/config.go +++ b/integration-tests/testconfig/vrfv2/config.go @@ -240,6 +240,7 @@ type General struct { VRFJobBatchFulfillmentGasMultiplier *float64 `toml:"vrf_job_batch_fulfillment_gas_multiplier"` VRFJobPollPeriod *blockchain.StrDuration `toml:"vrf_job_poll_period"` VRFJobRequestTimeout *blockchain.StrDuration `toml:"vrf_job_request_timeout"` + VRFJobSimulationBlock *string `toml:"vrf_job_simulation_block"` //BHS Job Config BHSJobWaitBlocks *int `toml:"bhs_job_wait_blocks"` @@ -378,5 +379,9 @@ func (c *General) Validate() error { return errors.New("bhs_job_wait_blocks must be set to a non-negative value") } + if c.VRFJobSimulationBlock != nil && (*c.VRFJobSimulationBlock != "latest" && *c.VRFJobSimulationBlock != "pending") { + return errors.New("simulation_block must be nil or \"latest\" or \"pending\"") + } + return nil } From ed80df25e58a6aa4a4cbd3556eceb829eca50656 Mon Sep 17 00:00:00 2001 From: jinhoonbang Date: Wed, 14 Feb 2024 08:50:26 -0800 Subject: [PATCH 07/10] Update core/chains/evm/client/simulated_backend_client.go Co-authored-by: Chris Cushman <104409744+vreff@users.noreply.github.com> --- core/chains/evm/client/simulated_backend_client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/chains/evm/client/simulated_backend_client.go b/core/chains/evm/client/simulated_backend_client.go index 918f5ce4045..2d1d9ec3665 100644 --- a/core/chains/evm/client/simulated_backend_client.go +++ b/core/chains/evm/client/simulated_backend_client.go @@ -568,7 +568,7 @@ func (c *SimulatedBackendClient) ethGetBlockByNumber(ctx context.Context, result } func (c *SimulatedBackendClient) ethEstimateGas(ctx context.Context, result interface{}, args ...interface{}) error { if len(args) != 2 { - return fmt.Errorf("SimulatedBackendClient expected 2 args, got %d for eth_call", len(args)) + return fmt.Errorf("SimulatedBackendClient expected 2 args, got %d for eth_estimateGas", len(args)) } params, ok := args[0].(map[string]interface{}) From 7e6e498d017205056ed852766016522fe9e13c12 Mon Sep 17 00:00:00 2001 From: jinhoonbang Date: Wed, 14 Feb 2024 11:51:27 -0800 Subject: [PATCH 08/10] use pendingContractCall instead of low-level call contract --- common/client/mock_rpc_test.go | 30 +++++++ common/client/multi_node.go | 11 +++ common/client/types.go | 4 + core/chains/evm/client/chain_client.go | 4 + core/chains/evm/client/client.go | 5 ++ core/chains/evm/client/erroring_node.go | 4 + core/chains/evm/client/mocks/client.go | 30 +++++++ core/chains/evm/client/node.go | 28 ++++++ core/chains/evm/client/null_client.go | 5 ++ core/chains/evm/client/pool.go | 4 + core/chains/evm/client/rpc_client.go | 28 ++++++ .../evm/client/simulated_backend_client.go | 41 ++++----- core/chains/evm/mocks/node.go | 30 +++++++ .../internal/cltest/contract_mock_receiver.go | 70 +++------------ core/scripts/common/vrf/jobs/jobs.go | 8 +- .../keeper/registry1_1_synchronizer_test.go | 22 ++--- .../keeper/registry1_2_synchronizer_test.go | 18 ++-- .../keeper/registry1_3_synchronizer_test.go | 20 ++--- .../registry_synchronizer_helper_test.go | 6 +- core/services/keeper/upkeep_executer_test.go | 52 +++++------ core/services/pipeline/task.eth_call.go | 45 +++++----- integration-tests/smoke/vrfv2_test.go | 86 ------------------- 22 files changed, 300 insertions(+), 251 deletions(-) diff --git a/common/client/mock_rpc_test.go b/common/client/mock_rpc_test.go index d87a02d47c1..72c6eb19029 100644 --- a/common/client/mock_rpc_test.go +++ b/common/client/mock_rpc_test.go @@ -426,6 +426,36 @@ func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS return r0, r1 } +// PendingCallContract provides a mock function with given fields: ctx, msg +func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) PendingCallContract(ctx context.Context, msg interface{}) ([]byte, error) { + ret := _m.Called(ctx, msg) + + if len(ret) == 0 { + panic("no return value specified for PendingCallContract") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, interface{}) ([]byte, error)); ok { + return rf(ctx, msg) + } + if rf, ok := ret.Get(0).(func(context.Context, interface{}) []byte); ok { + r0 = rf(ctx, msg) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, interface{}) error); ok { + r1 = rf(ctx, msg) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // PendingSequenceAt provides a mock function with given fields: ctx, addr func (_m *mockRPC[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD]) PendingSequenceAt(ctx context.Context, addr ADDR) (SEQ, error) { ret := _m.Called(ctx, addr) diff --git a/common/client/multi_node.go b/common/client/multi_node.go index 7d55784e68f..cbe1eb64e43 100644 --- a/common/client/multi_node.go +++ b/common/client/multi_node.go @@ -449,6 +449,17 @@ func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OP return n.RPC().CallContract(ctx, attempt, blockNumber) } +func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) PendingCallContract( + ctx context.Context, + attempt interface{}, +) (rpcErr []byte, extractErr error) { + n, err := c.selectNode() + if err != nil { + return rpcErr, err + } + return n.RPC().PendingCallContract(ctx, attempt) +} + // ChainID makes a direct RPC call. In most cases it should be better to use the configured chain id instead by // calling ConfiguredChainID. func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT]) ChainID(ctx context.Context) (id CHAIN_ID, err error) { diff --git a/common/client/types.go b/common/client/types.go index 32d4da98b50..fe9e4d7d482 100644 --- a/common/client/types.go +++ b/common/client/types.go @@ -124,6 +124,10 @@ type clientAPI[ msg interface{}, blockNumber *big.Int, ) (rpcErr []byte, extractErr error) + PendingCallContract( + ctx context.Context, + msg interface{}, + ) (rpcErr []byte, extractErr error) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error CodeAt(ctx context.Context, account ADDR, blockNumber *big.Int) ([]byte, error) } diff --git a/core/chains/evm/client/chain_client.go b/core/chains/evm/client/chain_client.go index 5dd70992382..7de589413e1 100644 --- a/core/chains/evm/client/chain_client.go +++ b/core/chains/evm/client/chain_client.go @@ -127,6 +127,10 @@ func (c *chainClient) CallContract(ctx context.Context, msg ethereum.CallMsg, bl return c.multiNode.CallContract(ctx, msg, blockNumber) } +func (c *chainClient) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) { + return c.multiNode.PendingCallContract(ctx, msg) +} + // TODO-1663: change this to actual ChainID() call once client.go is deprecated. func (c *chainClient) ChainID() (*big.Int, error) { //return c.multiNode.ChainID(ctx), nil diff --git a/core/chains/evm/client/client.go b/core/chains/evm/client/client.go index 61635c59c6b..e2ae8c26403 100644 --- a/core/chains/evm/client/client.go +++ b/core/chains/evm/client/client.go @@ -91,6 +91,7 @@ type Client interface { HeaderByHash(ctx context.Context, h common.Hash) (*types.Header, error) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) + PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) IsL2() bool } @@ -260,6 +261,10 @@ func (client *client) CallContract(ctx context.Context, msg ethereum.CallMsg, bl return client.pool.CallContract(ctx, msg, blockNumber) } +func (client *client) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) { + return client.pool.PendingCallContract(ctx, msg) +} + func (client *client) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) { return client.pool.CodeAt(ctx, account, blockNumber) } diff --git a/core/chains/evm/client/erroring_node.go b/core/chains/evm/client/erroring_node.go index c33891728a7..059f76d608a 100644 --- a/core/chains/evm/client/erroring_node.go +++ b/core/chains/evm/client/erroring_node.go @@ -103,6 +103,10 @@ func (e *erroringNode) CallContract(ctx context.Context, msg ethereum.CallMsg, b return nil, errors.New(e.errMsg) } +func (e *erroringNode) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) { + return nil, errors.New(e.errMsg) +} + func (e *erroringNode) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) { return nil, errors.New(e.errMsg) } diff --git a/core/chains/evm/client/mocks/client.go b/core/chains/evm/client/mocks/client.go index 0b45894cf28..bbaaafd7615 100644 --- a/core/chains/evm/client/mocks/client.go +++ b/core/chains/evm/client/mocks/client.go @@ -585,6 +585,36 @@ func (_m *Client) NodeStates() map[string]string { return r0 } +// PendingCallContract provides a mock function with given fields: ctx, msg +func (_m *Client) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) { + ret := _m.Called(ctx, msg) + + if len(ret) == 0 { + panic("no return value specified for PendingCallContract") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg) ([]byte, error)); ok { + return rf(ctx, msg) + } + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg) []byte); ok { + r0 = rf(ctx, msg) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ethereum.CallMsg) error); ok { + r1 = rf(ctx, msg) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // PendingCodeAt provides a mock function with given fields: ctx, account func (_m *Client) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { ret := _m.Called(ctx, account) diff --git a/core/chains/evm/client/node.go b/core/chains/evm/client/node.go index a27321535ed..aa472d605a6 100644 --- a/core/chains/evm/client/node.go +++ b/core/chains/evm/client/node.go @@ -117,6 +117,7 @@ type Node interface { EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) SuggestGasPrice(ctx context.Context) (*big.Int, error) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) + PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) HeaderByNumber(context.Context, *big.Int) (*types.Header, error) HeaderByHash(context.Context, common.Hash) (*types.Header, error) @@ -830,6 +831,33 @@ func (n *node) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumb } +func (n *node) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) (val []byte, err error) { + ctx, cancel, ws, http, err := n.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return nil, err + } + defer cancel() + lggr := n.newRqLggr().With("callMsg", msg) + + lggr.Debug("RPC call: evmclient.Client#PendingCallContract") + start := time.Now() + if http != nil { + val, err = http.geth.PendingCallContract(ctx, msg) + err = n.wrapHTTP(err) + } else { + val, err = ws.geth.PendingCallContract(ctx, msg) + err = n.wrapWS(err) + } + duration := time.Since(start) + + n.logResult(lggr, err, duration, n.getRPCDomain(), "PendingCallContract", + "val", val, + ) + + return + +} + func (n *node) BlockByNumber(ctx context.Context, number *big.Int) (b *types.Block, err error) { ctx, cancel, ws, http, err := n.makeLiveQueryCtxAndSafeGetClients(ctx) if err != nil { diff --git a/core/chains/evm/client/null_client.go b/core/chains/evm/client/null_client.go index e3bb1defd0d..3cbae9e9dde 100644 --- a/core/chains/evm/client/null_client.go +++ b/core/chains/evm/client/null_client.go @@ -196,6 +196,11 @@ func (nc *NullClient) CallContract(ctx context.Context, msg ethereum.CallMsg, bl return nil, nil } +func (nc *NullClient) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) { + nc.lggr.Debug("PendingCallContract") + return nil, nil +} + func (nc *NullClient) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) { nc.lggr.Debug("CodeAt") return nil, nil diff --git a/core/chains/evm/client/pool.go b/core/chains/evm/client/pool.go index b2d5a4847a5..3c33b3dbd0a 100644 --- a/core/chains/evm/client/pool.go +++ b/core/chains/evm/client/pool.go @@ -477,6 +477,10 @@ func (p *Pool) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumb return p.selectNode().CallContract(ctx, msg, blockNumber) } +func (p *Pool) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) { + return p.selectNode().PendingCallContract(ctx, msg) +} + func (p *Pool) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) { return p.selectNode().CodeAt(ctx, account, blockNumber) } diff --git a/core/chains/evm/client/rpc_client.go b/core/chains/evm/client/rpc_client.go index ce3a67162ed..54656cf1d3e 100644 --- a/core/chains/evm/client/rpc_client.go +++ b/core/chains/evm/client/rpc_client.go @@ -792,6 +792,34 @@ func (r *rpcClient) CallContract(ctx context.Context, msg interface{}, blockNumb } +func (r *rpcClient) PendingCallContract(ctx context.Context, msg interface{}) (val []byte, err error) { + ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + if err != nil { + return nil, err + } + defer cancel() + lggr := r.newRqLggr().With("callMsg", msg) + message := msg.(ethereum.CallMsg) + + lggr.Debug("RPC call: evmclient.Client#PendingCallContract") + start := time.Now() + if http != nil { + val, err = http.geth.PendingCallContract(ctx, message) + err = r.wrapHTTP(err) + } else { + val, err = ws.geth.PendingCallContract(ctx, message) + err = r.wrapWS(err) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "PendingCallContract", + "val", val, + ) + + return + +} + func (r *rpcClient) LatestBlockHeight(ctx context.Context) (*big.Int, error) { var height big.Int h, err := r.BlockNumber(ctx) diff --git a/core/chains/evm/client/simulated_backend_client.go b/core/chains/evm/client/simulated_backend_client.go index 2d1d9ec3665..3a235b8db40 100644 --- a/core/chains/evm/client/simulated_backend_client.go +++ b/core/chains/evm/client/simulated_backend_client.go @@ -403,6 +403,25 @@ func (c *SimulatedBackendClient) CallContract(ctx context.Context, msg ethereum. return res, nil } +func (c *SimulatedBackendClient) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) { + // Expected error is + // type JsonError struct { + // Code int `json:"code"` + // Message string `json:"message"` + // Data interface{} `json:"data,omitempty"` + //} + res, err := c.b.PendingCallContract(ctx, msg) + if err != nil { + dataErr := revertError{} + if errors.Is(err, &dataErr) { + return nil, &JsonError{Data: dataErr.ErrorData(), Message: dataErr.Error(), Code: 3} + } + // Generic revert, no data + return nil, &JsonError{Data: []byte{}, Message: err.Error(), Code: 3} + } + return res, nil +} + // CodeAt gets the code associated with an account as of a specified block. func (c *SimulatedBackendClient) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) { return c.b.CodeAt(ctx, account, blockNumber) @@ -709,28 +728,6 @@ func toCallMsg(params map[string]interface{}) ethereum.CallMsg { callMsg.Value = value } - switch gas := params["gas"].(type) { - case nil: - // This parameter is not required so nil is acceptable - case uint64: - callMsg.Gas = gas - case hexutil.Uint64: - callMsg.Gas = uint64(gas) - default: - panic("unexpected type of 'gas' parameter; try hexutil.Uint64, or uint64") - } - - switch gasPrice := params["gasPrice"].(type) { - case nil: - // This parameter is not required so nil is acceptable - case *big.Int: - callMsg.GasPrice = gasPrice - case *hexutil.Big: - callMsg.GasPrice = gasPrice.ToInt() - default: - panic("unexpected type of 'gasPrice' parameter; try *big.Int, or *hexutil.Big") - } - return callMsg } diff --git a/core/chains/evm/mocks/node.go b/core/chains/evm/mocks/node.go index 8f27218aec7..25944cfcf42 100644 --- a/core/chains/evm/mocks/node.go +++ b/core/chains/evm/mocks/node.go @@ -496,6 +496,36 @@ func (_m *Node) Order() int32 { return r0 } +// PendingCallContract provides a mock function with given fields: ctx, msg +func (_m *Node) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) { + ret := _m.Called(ctx, msg) + + if len(ret) == 0 { + panic("no return value specified for PendingCallContract") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg) ([]byte, error)); ok { + return rf(ctx, msg) + } + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg) []byte); ok { + r0 = rf(ctx, msg) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ethereum.CallMsg) error); ok { + r1 = rf(ctx, msg) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // PendingCodeAt provides a mock function with given fields: ctx, account func (_m *Node) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { ret := _m.Called(ctx, account) diff --git a/core/internal/cltest/contract_mock_receiver.go b/core/internal/cltest/contract_mock_receiver.go index 285e8ab9051..2a0fc5a50f5 100644 --- a/core/internal/cltest/contract_mock_receiver.go +++ b/core/internal/cltest/contract_mock_receiver.go @@ -35,7 +35,7 @@ type contractMockReceiver struct { address common.Address } -func (receiver contractMockReceiver) MockCallContractResponse(funcName string, responseArgs ...interface{}) *mock.Call { +func (receiver contractMockReceiver) MockResponse(funcName string, responseArgs ...interface{}) *mock.Call { funcSig := hexutil.Encode(receiver.abi.Methods[funcName].ID) if len(funcSig) != funcSigLength { receiver.t.Fatalf("Unable to find Registry contract function with name %s", funcName) @@ -55,7 +55,7 @@ func (receiver contractMockReceiver) MockCallContractResponse(funcName string, r Return(encoded, nil) } -func (receiver contractMockReceiver) MockCallContextResponse(funcName string, responseArgs ...interface{}) *mock.Call { +func (receiver contractMockReceiver) MockMatchedResponse(funcName string, matcher func(callArgs ethereum.CallMsg) bool, responseArgs ...interface{}) *mock.Call { funcSig := hexutil.Encode(receiver.abi.Methods[funcName].ID) if len(funcSig) != funcSigLength { receiver.t.Fatalf("Unable to find Registry contract function with name %s", funcName) @@ -65,55 +65,18 @@ func (receiver contractMockReceiver) MockCallContextResponse(funcName string, re return receiver.ethMock. On( - "CallContext", - mock.Anything, - mock.Anything, - "eth_call", - mock.MatchedBy(func(args map[string]interface{}) bool { - to := args["to"].(*common.Address) - data := args["input"].(hexutil.Bytes) - return *to == receiver.address && - hexutil.Encode(data)[0:funcSigLength] == funcSig - }), - mock.Anything). - Return(nil).Run(func(args mock.Arguments) { - resp := args.Get(1).(*hexutil.Bytes) - *resp = encoded - }) - -} - -func (receiver contractMockReceiver) MockCallContextMatchedResponse(funcName string, matcher func(args map[string]interface{}) bool, responseArgs ...interface{}) *mock.Call { - funcSig := hexutil.Encode(receiver.abi.Methods[funcName].ID) - if len(funcSig) != funcSigLength { - receiver.t.Fatalf("Unable to find Registry contract function with name %s", funcName) - } - - encoded := receiver.mustEncodeResponse(funcName, responseArgs...) - - // TODO: ALL CALLER MATCHER FUNCTIONS SHOULD BE CHANGED - - return receiver.ethMock. - On( - "CallContext", - mock.Anything, + "CallContract", mock.Anything, - "eth_call", - mock.MatchedBy(func(args map[string]interface{}) bool { - to := args["to"].(*common.Address) - data := args["input"].(hexutil.Bytes) - return *to == receiver.address && - hexutil.Encode(data)[0:funcSigLength] == funcSig && - matcher(args) + mock.MatchedBy(func(callArgs ethereum.CallMsg) bool { + return *callArgs.To == receiver.address && + hexutil.Encode(callArgs.Data)[0:funcSigLength] == funcSig && + matcher(callArgs) }), mock.Anything). - Return(nil).Run(func(args mock.Arguments) { - resp := args.Get(1).(*hexutil.Bytes) - *resp = encoded - }) + Return(encoded, nil) } -func (receiver contractMockReceiver) MockCallContextRevertResponse(funcName string) *mock.Call { +func (receiver contractMockReceiver) MockRevertResponse(funcName string) *mock.Call { funcSig := hexutil.Encode(receiver.abi.Methods[funcName].ID) if len(funcSig) != funcSigLength { receiver.t.Fatalf("Unable to find Registry contract function with name %s", funcName) @@ -121,19 +84,14 @@ func (receiver contractMockReceiver) MockCallContextRevertResponse(funcName stri return receiver.ethMock. On( - "CallContext", - mock.Anything, + "CallContract", mock.Anything, - "eth_call", - mock.MatchedBy(func(args map[string]interface{}) bool { - to := args["to"].(*common.Address) - data := args["input"].(hexutil.Bytes) - return *to == receiver.address && - hexutil.Encode(data)[0:funcSigLength] == funcSig + mock.MatchedBy(func(callArgs ethereum.CallMsg) bool { + return *callArgs.To == receiver.address && + hexutil.Encode(callArgs.Data)[0:funcSigLength] == funcSig }), mock.Anything). - Return(errors.New("revert")) - + Return(nil, errors.New("revert")) } func (receiver contractMockReceiver) mustEncodeResponse(funcName string, responseArgs ...interface{}) []byte { diff --git a/core/scripts/common/vrf/jobs/jobs.go b/core/scripts/common/vrf/jobs/jobs.go index 16f892e75f6..66bdf712e5c 100644 --- a/core/scripts/common/vrf/jobs/jobs.go +++ b/core/scripts/common/vrf/jobs/jobs.go @@ -28,7 +28,7 @@ estimate_gas [type=estimategaslimit to="%s" multiplier="%f" data="$(vrf.output)" - block=%s] + block="%s"] simulate [type=ethcall from="%s" to="%s" @@ -37,7 +37,7 @@ simulate [type=ethcall extractRevertReason=true contract="%s" data="$(vrf.output)" - block=%s] + block="%s"] decode_log->vrf->estimate_gas->simulate """` @@ -69,7 +69,7 @@ estimate_gas [type=estimategaslimit to="%s" multiplier="%f" data="$(generate_proof.output)" - block=%s] + block="%s"] simulate_fulfillment [type=ethcall from="%s" to="%s" @@ -78,7 +78,7 @@ simulate_fulfillment [type=ethcall extractRevertReason=true contract="%s" data="$(generate_proof.output)" - block=%s] + block="%s"] decode_log->generate_proof->estimate_gas->simulate_fulfillment """ ` diff --git a/core/services/keeper/registry1_1_synchronizer_test.go b/core/services/keeper/registry1_1_synchronizer_test.go index 4cc3f73dc3f..a4f03d4d34a 100644 --- a/core/services/keeper/registry1_1_synchronizer_test.go +++ b/core/services/keeper/registry1_1_synchronizer_test.go @@ -61,12 +61,12 @@ func mockRegistry1_1( ethMock.On("HeadByNumber", mock.Anything, (*big.Int)(nil)). Return(&evmtypes.Head{Number: 10}, nil) - registryMock.MockCallContractResponse("getConfig", config).Once() - registryMock.MockCallContractResponse("getKeeperList", keeperList).Once() - registryMock.MockCallContractResponse("getCanceledUpkeepList", cancelledUpkeeps).Once() - registryMock.MockCallContractResponse("getUpkeepCount", upkeepCount).Once() + registryMock.MockResponse("getConfig", config).Once() + registryMock.MockResponse("getKeeperList", keeperList).Once() + registryMock.MockResponse("getCanceledUpkeepList", cancelledUpkeeps).Once() + registryMock.MockResponse("getUpkeepCount", upkeepCount).Once() if timesGetUpkeepMock > 0 { - registryMock.MockCallContractResponse("getUpkeep", upkeepConfig).Times(timesGetUpkeepMock) + registryMock.MockResponse("getUpkeep", upkeepConfig).Times(timesGetUpkeepMock) } } @@ -79,7 +79,7 @@ func Test_LogListenerOpts1_1(t *testing.T) { contractAddress := j.KeeperSpec.ContractAddress.Address() registryMock := cltest.NewContractMockReceiver(t, ethClient, keeper.Registry1_1ABI, contractAddress) - registryMock.MockCallContractResponse("typeAndVersion", "KeeperRegistry 1.1.0").Once() + registryMock.MockResponse("typeAndVersion", "KeeperRegistry 1.1.0").Once() registryWrapper, err := keeper.NewRegistryWrapper(j.KeeperSpec.ContractAddress, ethClient) require.NoError(t, err) @@ -226,8 +226,8 @@ func Test_RegistrySynchronizer1_1_ConfigSetLog(t *testing.T) { registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_1ABI, contractAddress) newConfig := registryConfig1_1 newConfig.BlockCountPerTurn = big.NewInt(40) // change from default - registryMock.MockCallContractResponse("getKeeperList", []common.Address{fromAddress}).Once() - registryMock.MockCallContractResponse("getConfig", newConfig).Once() + registryMock.MockResponse("getKeeperList", []common.Address{fromAddress}).Once() + registryMock.MockResponse("getConfig", newConfig).Once() cfg := configtest.NewGeneralConfig(t, nil) head := cltest.MustInsertHead(t, db, cfg.Database(), 1) @@ -273,8 +273,8 @@ func Test_RegistrySynchronizer1_1_KeepersUpdatedLog(t *testing.T) { addresses := []common.Address{fromAddress, testutils.NewAddress()} // change from default registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_1ABI, contractAddress) - registryMock.MockCallContractResponse("getConfig", registryConfig1_1).Once() - registryMock.MockCallContractResponse("getKeeperList", addresses).Once() + registryMock.MockResponse("getConfig", registryConfig1_1).Once() + registryMock.MockResponse("getKeeperList", addresses).Once() cfg := configtest.NewGeneralConfig(t, nil) head := cltest.MustInsertHead(t, db, cfg.Database(), 1) @@ -355,7 +355,7 @@ func Test_RegistrySynchronizer1_1_UpkeepRegisteredLog(t *testing.T) { cltest.WaitForCount(t, db, "upkeep_registrations", 1) registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_1ABI, contractAddress) - registryMock.MockCallContractResponse("getUpkeep", upkeepConfig1_1).Once() + registryMock.MockResponse("getUpkeep", upkeepConfig1_1).Once() cfg := configtest.NewGeneralConfig(t, nil) head := cltest.MustInsertHead(t, db, cfg.Database(), 1) diff --git a/core/services/keeper/registry1_2_synchronizer_test.go b/core/services/keeper/registry1_2_synchronizer_test.go index 60fd913221d..b7456ad94e4 100644 --- a/core/services/keeper/registry1_2_synchronizer_test.go +++ b/core/services/keeper/registry1_2_synchronizer_test.go @@ -83,13 +83,13 @@ func mockRegistry1_2( ethMock.On("HeadByNumber", mock.Anything, (*big.Int)(nil)). Return(&evmtypes.Head{Number: 10}, nil) if getStateTime > 0 { - registryMock.MockCallContractResponse("getState", getState).Times(getStateTime) + registryMock.MockResponse("getState", getState).Times(getStateTime) } if getActiveUpkeepIDsTime > 0 { - registryMock.MockCallContractResponse("getActiveUpkeepIDs", activeUpkeepIDs).Times(getActiveUpkeepIDsTime) + registryMock.MockResponse("getActiveUpkeepIDs", activeUpkeepIDs).Times(getActiveUpkeepIDsTime) } if timesGetUpkeepMock > 0 { - registryMock.MockCallContractResponse("getUpkeep", upkeepConfig).Times(timesGetUpkeepMock) + registryMock.MockResponse("getUpkeep", upkeepConfig).Times(timesGetUpkeepMock) } } @@ -102,7 +102,7 @@ func Test_LogListenerOpts1_2(t *testing.T) { contractAddress := j.KeeperSpec.ContractAddress.Address() registryMock := cltest.NewContractMockReceiver(t, ethClient, keeper.Registry1_1ABI, contractAddress) - registryMock.MockCallContractResponse("typeAndVersion", "KeeperRegistry 1.2.0").Once() + registryMock.MockResponse("typeAndVersion", "KeeperRegistry 1.2.0").Once() registryWrapper, err := keeper.NewRegistryWrapper(j.KeeperSpec.ContractAddress, ethClient) require.NoError(t, err) @@ -246,7 +246,7 @@ func Test_RegistrySynchronizer1_2_ConfigSetLog(t *testing.T) { registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_2ABI, contractAddress) newConfig := registryConfig1_2 newConfig.BlockCountPerTurn = big.NewInt(40) // change from default - registryMock.MockCallContractResponse("getState", registry1_2.GetState{ + registryMock.MockResponse("getState", registry1_2.GetState{ State: registryState1_2, Config: newConfig, Keepers: []common.Address{fromAddress}, @@ -297,7 +297,7 @@ func Test_RegistrySynchronizer1_2_KeepersUpdatedLog(t *testing.T) { addresses := []common.Address{fromAddress, testutils.NewAddress()} // change from default registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_2ABI, contractAddress) - registryMock.MockCallContractResponse("getState", registry1_2.GetState{ + registryMock.MockResponse("getState", registry1_2.GetState{ State: registryState1_2, Config: registryConfig1_2, Keepers: addresses, @@ -385,7 +385,7 @@ func Test_RegistrySynchronizer1_2_UpkeepRegisteredLog(t *testing.T) { cltest.WaitForCount(t, db, "upkeep_registrations", 1) registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_2ABI, contractAddress) - registryMock.MockCallContractResponse("getUpkeep", upkeepConfig1_2).Once() + registryMock.MockResponse("getUpkeep", upkeepConfig1_2).Once() cfg := configtest.NewGeneralConfig(t, nil) head := cltest.MustInsertHead(t, db, cfg.Database(), 1) @@ -493,7 +493,7 @@ func Test_RegistrySynchronizer1_2_UpkeepGasLimitSetLog(t *testing.T) { registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_2ABI, contractAddress) newConfig := upkeepConfig1_2 newConfig.ExecuteGas = 4_000_000 // change from default - registryMock.MockCallContractResponse("getUpkeep", newConfig).Once() + registryMock.MockResponse("getUpkeep", newConfig).Once() cfg := configtest.NewGeneralConfig(t, nil) head := cltest.MustInsertHead(t, db, cfg.Database(), 1) @@ -535,7 +535,7 @@ func Test_RegistrySynchronizer1_2_UpkeepReceivedLog(t *testing.T) { cltest.WaitForCount(t, db, "upkeep_registrations", 1) registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_2ABI, contractAddress) - registryMock.MockCallContractResponse("getUpkeep", upkeepConfig1_2).Once() + registryMock.MockResponse("getUpkeep", upkeepConfig1_2).Once() cfg := configtest.NewGeneralConfig(t, nil) head := cltest.MustInsertHead(t, db, cfg.Database(), 1) diff --git a/core/services/keeper/registry1_3_synchronizer_test.go b/core/services/keeper/registry1_3_synchronizer_test.go index 031e305b83c..77bb873e1d0 100644 --- a/core/services/keeper/registry1_3_synchronizer_test.go +++ b/core/services/keeper/registry1_3_synchronizer_test.go @@ -85,13 +85,13 @@ func mockRegistry1_3( ethMock.On("HeadByNumber", mock.Anything, (*big.Int)(nil)). Return(&evmtypes.Head{Number: 10}, nil) if getStateTime > 0 { - registryMock.MockCallContractResponse("getState", getState).Times(getStateTime) + registryMock.MockResponse("getState", getState).Times(getStateTime) } if getActiveUpkeepIDsTime > 0 { - registryMock.MockCallContractResponse("getActiveUpkeepIDs", activeUpkeepIDs).Times(getActiveUpkeepIDsTime) + registryMock.MockResponse("getActiveUpkeepIDs", activeUpkeepIDs).Times(getActiveUpkeepIDsTime) } if timesGetUpkeepMock > 0 { - registryMock.MockCallContractResponse("getUpkeep", upkeepConfig).Times(timesGetUpkeepMock) + registryMock.MockResponse("getUpkeep", upkeepConfig).Times(timesGetUpkeepMock) } } @@ -104,7 +104,7 @@ func Test_LogListenerOpts1_3(t *testing.T) { contractAddress := j.KeeperSpec.ContractAddress.Address() registryMock := cltest.NewContractMockReceiver(t, ethClient, keeper.Registry1_1ABI, contractAddress) - registryMock.MockCallContractResponse("typeAndVersion", "KeeperRegistry 1.3.0").Once() + registryMock.MockResponse("typeAndVersion", "KeeperRegistry 1.3.0").Once() registryWrapper, err := keeper.NewRegistryWrapper(j.KeeperSpec.ContractAddress, ethClient) require.NoError(t, err) @@ -251,7 +251,7 @@ func Test_RegistrySynchronizer1_3_ConfigSetLog(t *testing.T) { registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_3ABI, contractAddress) newConfig := registryConfig1_3 newConfig.BlockCountPerTurn = big.NewInt(40) // change from default - registryMock.MockCallContractResponse("getState", registry1_3.GetState{ + registryMock.MockResponse("getState", registry1_3.GetState{ State: registryState1_3, Config: newConfig, Keepers: []common.Address{fromAddress}, @@ -302,7 +302,7 @@ func Test_RegistrySynchronizer1_3_KeepersUpdatedLog(t *testing.T) { addresses := []common.Address{fromAddress, testutils.NewAddress()} // change from default registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_3ABI, contractAddress) - registryMock.MockCallContractResponse("getState", registry1_3.GetState{ + registryMock.MockResponse("getState", registry1_3.GetState{ State: registryState1_3, Config: registryConfig1_3, Keepers: addresses, @@ -390,7 +390,7 @@ func Test_RegistrySynchronizer1_3_UpkeepRegisteredLog(t *testing.T) { cltest.WaitForCount(t, db, "upkeep_registrations", 1) registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_3ABI, contractAddress) - registryMock.MockCallContractResponse("getUpkeep", upkeepConfig1_3).Once() + registryMock.MockResponse("getUpkeep", upkeepConfig1_3).Once() cfg := configtest.NewGeneralConfig(t, nil) head := cltest.MustInsertHead(t, db, cfg.Database(), 1) @@ -498,7 +498,7 @@ func Test_RegistrySynchronizer1_3_UpkeepGasLimitSetLog(t *testing.T) { registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_3ABI, contractAddress) newConfig := upkeepConfig1_3 newConfig.ExecuteGas = 4_000_000 // change from default - registryMock.MockCallContractResponse("getUpkeep", newConfig).Once() + registryMock.MockResponse("getUpkeep", newConfig).Once() cfg := configtest.NewGeneralConfig(t, nil) head := cltest.MustInsertHead(t, db, cfg.Database(), 1) @@ -540,7 +540,7 @@ func Test_RegistrySynchronizer1_3_UpkeepReceivedLog(t *testing.T) { cltest.WaitForCount(t, db, "upkeep_registrations", 1) registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_3ABI, contractAddress) - registryMock.MockCallContractResponse("getUpkeep", upkeepConfig1_3).Once() + registryMock.MockResponse("getUpkeep", upkeepConfig1_3).Once() cfg := configtest.NewGeneralConfig(t, nil) head := cltest.MustInsertHead(t, db, cfg.Database(), 1) @@ -699,7 +699,7 @@ func Test_RegistrySynchronizer1_3_UpkeepCheckDataUpdatedLog(t *testing.T) { registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_3ABI, contractAddress) newConfig := upkeepConfig1_3 newConfig.CheckData = newCheckData // changed from default - registryMock.MockCallContractResponse("getUpkeep", newConfig).Once() + registryMock.MockResponse("getUpkeep", newConfig).Once() updatedLog := registry1_3.KeeperRegistryUpkeepCheckDataUpdated{Id: upkeepId, NewCheckData: newCheckData} logBroadcast := logmocks.NewBroadcast(t) diff --git a/core/services/keeper/registry_synchronizer_helper_test.go b/core/services/keeper/registry_synchronizer_helper_test.go index 94545bc040b..19ba2eedbbb 100644 --- a/core/services/keeper/registry_synchronizer_helper_test.go +++ b/core/services/keeper/registry_synchronizer_helper_test.go @@ -54,13 +54,13 @@ func setupRegistrySync(t *testing.T, version keeper.RegistryVersion) ( switch version { case keeper.RegistryVersion_1_0, keeper.RegistryVersion_1_1: registryMock := cltest.NewContractMockReceiver(t, ethClient, keeper.Registry1_1ABI, contractAddress) - registryMock.MockCallContractResponse("typeAndVersion", "KeeperRegistry 1.1.1").Once() + registryMock.MockResponse("typeAndVersion", "KeeperRegistry 1.1.1").Once() case keeper.RegistryVersion_1_2: registryMock := cltest.NewContractMockReceiver(t, ethClient, keeper.Registry1_2ABI, contractAddress) - registryMock.MockCallContractResponse("typeAndVersion", "KeeperRegistry 1.2.0").Once() + registryMock.MockResponse("typeAndVersion", "KeeperRegistry 1.2.0").Once() case keeper.RegistryVersion_1_3: registryMock := cltest.NewContractMockReceiver(t, ethClient, keeper.Registry1_3ABI, contractAddress) - registryMock.MockCallContractResponse("typeAndVersion", "KeeperRegistry 1.3.0").Once() + registryMock.MockResponse("typeAndVersion", "KeeperRegistry 1.3.0").Once() case keeper.RegistryVersion_2_0, keeper.RegistryVersion_2_1: t.Fatalf("Unsupported version: %s", version) } diff --git a/core/services/keeper/upkeep_executer_test.go b/core/services/keeper/upkeep_executer_test.go index 6700a04c8d8..590c9720cb2 100644 --- a/core/services/keeper/upkeep_executer_test.go +++ b/core/services/keeper/upkeep_executer_test.go @@ -6,8 +6,8 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/onsi/gomega" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -146,19 +146,17 @@ func Test_UpkeepExecuter_PerformsUpkeep_Happy(t *testing.T) { Run(func(mock.Arguments) { ethTxCreated.ItHappened() }) registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_1ABI, registry.ContractAddress.Address()) - registryMock.MockCallContextMatchedResponse( + registryMock.MockMatchedResponse( "checkUpkeep", - func(args map[string]interface{}) bool { - gasPrice := args["gasPrice"].(*hexutil.Big) - gas := args["gas"].(hexutil.Uint64) - return gasPrice == nil && - gas == 0 + func(callArgs ethereum.CallMsg) bool { + return callArgs.GasPrice == nil && + callArgs.Gas == 0 }, checkUpkeepResponse, ) - registryMock.MockCallContextMatchedResponse( + registryMock.MockMatchedResponse( "performUpkeep", - func(args map[string]interface{}) bool { return true }, + func(callArgs ethereum.CallMsg) bool { return true }, checkPerformResponse, ) @@ -193,19 +191,17 @@ func Test_UpkeepExecuter_PerformsUpkeep_Happy(t *testing.T) { Run(func(mock.Arguments) { ethTxCreated.ItHappened() }) registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_1ABI, registry.ContractAddress.Address()) - registryMock.MockCallContextMatchedResponse( + registryMock.MockMatchedResponse( "checkUpkeep", - func(args map[string]interface{}) bool { - gasPrice := args["gasPrice"].(*hexutil.Big) - gas := args["gas"].(hexutil.Uint64) - return gasPrice == nil && - gas == 0 + func(callArgs ethereum.CallMsg) bool { + return callArgs.GasPrice == nil && + callArgs.Gas == 0 }, checkUpkeepResponse, ) - registryMock.MockCallContextMatchedResponse( + registryMock.MockMatchedResponse( "performUpkeep", - func(args map[string]interface{}) bool { return true }, + func(callArgs ethereum.CallMsg) bool { return true }, checkPerformResponse, ) @@ -241,19 +237,17 @@ func Test_UpkeepExecuter_PerformsUpkeep_Happy(t *testing.T) { require.NoError(t, err) registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_1ABI, registry.ContractAddress.Address()) - registryMock.MockCallContextMatchedResponse( + registryMock.MockMatchedResponse( "checkUpkeep", - func(args map[string]interface{}) bool { - gasPrice := args["gasPrice"].(*hexutil.Big) - gas := args["gas"].(hexutil.Uint64) - return gasPrice == nil && - gas == 0 + func(callArgs ethereum.CallMsg) bool { + return callArgs.GasPrice == nil && + callArgs.Gas == 0 }, checkUpkeepResponse, ) - registryMock.MockCallContextMatchedResponse( + registryMock.MockMatchedResponse( "performUpkeep", - func(args map[string]interface{}) bool { return true }, + func(callArgs ethereum.CallMsg) bool { return true }, checkPerformResponse, ) @@ -302,10 +296,10 @@ func Test_UpkeepExecuter_PerformsUpkeep_Happy(t *testing.T) { Run(func(mock.Arguments) { etxs[0].ItHappened() }) registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_1ABI, registry.ContractAddress.Address()) - registryMock.MockCallContextResponse("checkUpkeep", checkUpkeepResponse) - registryMock.MockCallContextMatchedResponse( + registryMock.MockResponse("checkUpkeep", checkUpkeepResponse) + registryMock.MockMatchedResponse( "performUpkeep", - func(args map[string]interface{}) bool { return true }, + func(callArgs ethereum.CallMsg) bool { return true }, checkPerformResponse, ) // turn falls somewhere between 20-39 (blockCountPerTurn=20) @@ -333,7 +327,7 @@ func Test_UpkeepExecuter_PerformsUpkeep_Error(t *testing.T) { var wasCalled atomic.Bool registryMock := cltest.NewContractMockReceiver(t, ethMock, keeper.Registry1_1ABI, registry.ContractAddress.Address()) - registryMock.MockCallContextRevertResponse("checkUpkeep").Run(func(args mock.Arguments) { + registryMock.MockRevertResponse("checkUpkeep").Run(func(args mock.Arguments) { wasCalled.Store(true) }) diff --git a/core/services/pipeline/task.eth_call.go b/core/services/pipeline/task.eth_call.go index 3007f1d4bd6..f011cd7a9b6 100644 --- a/core/services/pipeline/task.eth_call.go +++ b/core/services/pipeline/task.eth_call.go @@ -3,10 +3,11 @@ package pipeline import ( "context" "fmt" + "strings" "time" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" @@ -118,29 +119,31 @@ func (t *ETHCallTask) Run(ctx context.Context, lggr logger.Logger, vars Vars, in } } - args := map[string]interface{}{ - "from": common.Address(from), - "to": (*common.Address)(&contractAddr), - "input": hexutil.Bytes([]byte(data)), - "gas": hexutil.Uint64(uint64(selectedGas)), - "gasPrice": (*hexutil.Big)(gasPrice.BigInt()), - "maxFeePerGas": (*hexutil.Big)(gasFeeCap.BigInt()), - "maxPriorityFeePerGas": (*hexutil.Big)(gasTipCap.BigInt()), + call := ethereum.CallMsg{ + To: (*common.Address)(&contractAddr), + From: (common.Address)(from), + Data: []byte(data), + Gas: uint64(selectedGas), + GasPrice: gasPrice.BigInt(), + GasTipCap: gasTipCap.BigInt(), + GasFeeCap: gasFeeCap.BigInt(), } - selectedBlock, err := selectBlock(string(block)) - if err != nil { - return Result{Error: err}, runInfo - } - - lggr = lggr.With("gas", uint64(selectedGas)). - With("gasPrice", gasPrice.BigInt()). - With("gasTipCap", gasTipCap.BigInt()). - With("gasFeeCap", gasFeeCap.BigInt()) + lggr = lggr.With("gas", call.Gas). + With("gasPrice", call.GasPrice). + With("gasTipCap", call.GasTipCap). + With("gasFeeCap", call.GasFeeCap) start := time.Now() - var resp hexutil.Bytes - err = chain.Client().CallContext(ctx, &resp, "eth_call", args, selectedBlock) + + var resp []byte + blockStr := block.String() + if blockStr == "" || strings.ToLower(blockStr) == "latest" { + resp, err = chain.Client().CallContract(ctx, call, nil) + } else if strings.ToLower(blockStr) == "pending" { + resp, err = chain.Client().PendingCallContract(ctx, call) + } + elapsed := time.Since(start) if err != nil { if t.ExtractRevertReason { @@ -158,5 +161,5 @@ func (t *ETHCallTask) Run(ctx context.Context, lggr logger.Logger, vars Vars, in } promETHCallTime.WithLabelValues(t.DotID()).Set(float64(elapsed)) - return Result{Value: []byte(resp)}, runInfo + return Result{Value: resp}, runInfo } diff --git a/integration-tests/smoke/vrfv2_test.go b/integration-tests/smoke/vrfv2_test.go index 5d77bbd1a11..c289cd019c1 100644 --- a/integration-tests/smoke/vrfv2_test.go +++ b/integration-tests/smoke/vrfv2_test.go @@ -866,89 +866,3 @@ func TestVRFV2WithBHS(t *testing.T) { require.Equal(t, 0, randomWordsRequestedEvent.Raw.BlockHash.Cmp(randRequestBlockHash)) }) } - -func TestVRFV2PendingBlockSimulation(t *testing.T) { - t.Parallel() - l := logging.GetTestLogger(t) - - config, err := tc.GetConfig("Smoke", tc.VRFv2) - require.NoError(t, err, "Error getting config") - - config.VRFv2.General.VRFJobSimulationBlock = ptr.Ptr[string]("pending") - useVRFOwner := false - useTestCoordinator := false - network, err := actions.EthereumNetworkConfigFromConfig(l, &config) - require.NoError(t, err, "Error building ethereum network config") - - env, err := test_env.NewCLTestEnvBuilder(). - WithTestInstance(t). - WithTestConfig(&config). - WithPrivateEthereumNetwork(network). - WithCLNodes(1). - WithFunding(big.NewFloat(*config.Common.ChainlinkNodeFunding)). - WithStandardCleanup(). - Build() - require.NoError(t, err, "error creating test env") - - env.ParallelTransactions(true) - - mockETHLinkFeed, err := actions.DeployMockETHLinkFeed(env.ContractDeployer, big.NewInt(*config.VRFv2.General.LinkNativeFeedResponse)) - require.NoError(t, err) - linkToken, err := actions.DeployLINKToken(env.ContractDeployer) - require.NoError(t, err) - - // register proving key against oracle address (sending key) in order to test oracleWithdraw - defaultWalletAddress := env.EVMClient.GetDefaultWallet().Address() - - numberOfTxKeysToCreate := 1 - vrfv2Contracts, subIDs, vrfv2KeyData, nodesMap, err := vrfv2.SetupVRFV2Environment( - env, - []vrfcommon.VRFNodeType{vrfcommon.VRF}, - &config, - useVRFOwner, - useTestCoordinator, - linkToken, - mockETHLinkFeed, - defaultWalletAddress, - numberOfTxKeysToCreate, - 1, - 1, - l, - ) - require.NoError(t, err, "error setting up VRF v2 env") - - subID := subIDs[0] - - subscription, err := vrfv2Contracts.CoordinatorV2.GetSubscription(testcontext.Get(t), subID) - require.NoError(t, err, "error getting subscription information") - - vrfv2.LogSubDetails(l, subscription, subID, vrfv2Contracts.CoordinatorV2) - - jobRunsBeforeTest, err := nodesMap[vrfcommon.VRF].CLNode.API.MustReadRunsByJob(nodesMap[vrfcommon.VRF].Job.Data.ID) - require.NoError(t, err, "error reading job runs") - - // test and assert - randomWordsFulfilledEvent, err := vrfv2.RequestRandomnessAndWaitForFulfillment( - l, - vrfv2Contracts.VRFV2Consumer[0], - vrfv2Contracts.CoordinatorV2, - subID, - vrfv2KeyData, - *config.VRFv2.General.MinimumConfirmations, - *config.VRFv2.General.CallbackGasLimit, - *config.VRFv2.General.NumberOfWords, - *config.VRFv2.General.RandomnessRequestCountPerRequest, - *config.VRFv2.General.RandomnessRequestCountPerRequestDeviation, - config.VRFv2.General.RandomWordsFulfilledEventTimeout.Duration, - ) - require.NoError(t, err, "error requesting randomness and waiting for fulfilment") - - jobRuns, err := nodesMap[vrfcommon.VRF].CLNode.API.MustReadRunsByJob(nodesMap[vrfcommon.VRF].Job.Data.ID) - require.NoError(t, err, "error reading job runs") - require.Equal(t, len(jobRunsBeforeTest.Data)+1, len(jobRuns.Data)) - - status, err := vrfv2Contracts.VRFV2Consumer[0].GetRequestStatus(testcontext.Get(t), randomWordsFulfilledEvent.RequestId) - require.NoError(t, err, "error getting rand request status") - require.True(t, status.Fulfilled) - l.Debug().Bool("Fulfilment Status", status.Fulfilled).Msg("Random Words Request Fulfilment Status") -} From ef212f6a967bda30e32229f12b6666541d308e6e Mon Sep 17 00:00:00 2001 From: jinhoonbang Date: Wed, 14 Feb 2024 12:17:48 -0800 Subject: [PATCH 09/10] fix eth_call_test.go --- core/services/pipeline/task.eth_call_test.go | 183 +++++++------------ 1 file changed, 70 insertions(+), 113 deletions(-) diff --git a/core/services/pipeline/task.eth_call_test.go b/core/services/pipeline/task.eth_call_test.go index e91c1cbba84..28af94ba25c 100644 --- a/core/services/pipeline/task.eth_call_test.go +++ b/core/services/pipeline/task.eth_call_test.go @@ -6,7 +6,6 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -54,42 +53,25 @@ func TestETHCallTask(t *testing.T) { expectedErrorContains string }{ { - "happy with empty from", // name - "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", // contract - "", // from - "$(foo)", // data - "0", // evmChainID - "", // gas - "", // block - nil, // specGasLimit - pipeline.NewVarsFrom(map[string]interface{}{ // vars + "happy with empty from", + "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", + "", + "$(foo)", + "0", + "", + "", + nil, + pipeline.NewVarsFrom(map[string]interface{}{ "foo": []byte("foo bar"), }), - nil, // inputs - func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) { // setupClientMocks + nil, + func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) { contractAddr := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") ethClient. - On("CallContext", - mock.Anything, - mock.Anything, - "eth_call", - map[string]interface{}{ - "from": common.Address{}, - "to": &contractAddr, - "gas": hexutil.Uint64(uint64(drJobTypeGasLimit)), - "input": hexutil.Bytes([]byte("foo bar")), - "gasPrice": (*hexutil.Big)(nil), - "maxFeePerGas": (*hexutil.Big)(nil), - "maxPriorityFeePerGas": (*hexutil.Big)(nil), - }, - "latest").Return(nil).Run(func(args mock.Arguments) { - resp := args.Get(1).(*hexutil.Bytes) - *resp = []byte("baz quux") - }) + On("CallContract", mock.Anything, ethereum.CallMsg{To: &contractAddr, Gas: uint64(drJobTypeGasLimit), Data: []byte("foo bar")}, (*big.Int)(nil)). + Return([]byte("baz quux"), nil) }, - []byte("baz quux"), // expected - nil, // expecedErrorCause - "", // expectedErrorContains + []byte("baz quux"), nil, "", }, { "happy with gas limit per task", @@ -98,37 +80,20 @@ func TestETHCallTask(t *testing.T) { "$(foo)", "0", "$(gasLimit)", - "", // block + "", nil, pipeline.NewVarsFrom(map[string]interface{}{ "foo": []byte("foo bar"), "gasLimit": 100_000, }), nil, - func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) { // setupClientMocks + func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) { contractAddr := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") ethClient. - On("CallContext", - mock.Anything, - mock.Anything, - "eth_call", - map[string]interface{}{ - "from": common.Address{}, - "to": &contractAddr, - "gas": hexutil.Uint64(uint64(100_000)), - "input": hexutil.Bytes([]byte("foo bar")), - "gasPrice": (*hexutil.Big)(nil), - "maxFeePerGas": (*hexutil.Big)(nil), - "maxPriorityFeePerGas": (*hexutil.Big)(nil), - }, - "latest").Return(nil).Run(func(args mock.Arguments) { - resp := args.Get(1).(*hexutil.Bytes) - *resp = []byte("baz quux") - }) + On("CallContract", mock.Anything, ethereum.CallMsg{To: &contractAddr, Gas: 100_000, Data: []byte("foo bar")}, (*big.Int)(nil)). + Return([]byte("baz quux"), nil) }, - []byte("baz quux"), - nil, - "", + []byte("baz quux"), nil, "", }, { "happy with gas limit per spec", @@ -137,36 +102,19 @@ func TestETHCallTask(t *testing.T) { "$(foo)", "0", "", - "pending", // block + "", &specGasLimit, pipeline.NewVarsFrom(map[string]interface{}{ "foo": []byte("foo bar"), }), nil, - func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) { // setupClientMocks + func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) { contractAddr := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") ethClient. - On("CallContext", - mock.Anything, - mock.Anything, - "eth_call", - map[string]interface{}{ - "from": common.Address{}, - "to": &contractAddr, - "gas": hexutil.Uint64(uint64(specGasLimit)), - "input": hexutil.Bytes([]byte("foo bar")), - "gasPrice": (*hexutil.Big)(nil), - "maxFeePerGas": (*hexutil.Big)(nil), - "maxPriorityFeePerGas": (*hexutil.Big)(nil), - }, - "pending").Return(nil).Run(func(args mock.Arguments) { - resp := args.Get(1).(*hexutil.Bytes) - *resp = []byte("baz quux") - }) + On("CallContract", mock.Anything, ethereum.CallMsg{To: &contractAddr, Gas: uint64(specGasLimit), Data: []byte("foo bar")}, (*big.Int)(nil)). + Return([]byte("baz quux"), nil) }, - []byte("baz quux"), // expected - nil, - "", + []byte("baz quux"), nil, "", }, { "happy with from addr", @@ -175,33 +123,18 @@ func TestETHCallTask(t *testing.T) { "$(foo)", "0", "", - "pending", // block + "", nil, pipeline.NewVarsFrom(map[string]interface{}{ "foo": []byte("foo bar"), }), nil, - func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) { // setupClientMocks + func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) { contractAddr := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") fromAddr := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") ethClient. - On("CallContext", - mock.Anything, - mock.Anything, - "eth_call", - map[string]interface{}{ - "from": fromAddr, - "to": &contractAddr, - "gas": hexutil.Uint64(drJobTypeGasLimit), - "input": hexutil.Bytes([]byte("foo bar")), - "gasPrice": (*hexutil.Big)(nil), - "maxFeePerGas": (*hexutil.Big)(nil), - "maxPriorityFeePerGas": (*hexutil.Big)(nil), - }, - "pending").Return(nil).Run(func(args mock.Arguments) { - resp := args.Get(1).(*hexutil.Bytes) - *resp = []byte("baz quux") - }) + On("CallContract", mock.Anything, ethereum.CallMsg{To: &contractAddr, Gas: uint64(drJobTypeGasLimit), From: fromAddr, Data: []byte("foo bar")}, (*big.Int)(nil)). + Return([]byte("baz quux"), nil) }, []byte("baz quux"), nil, "", }, @@ -285,24 +218,6 @@ func TestETHCallTask(t *testing.T) { func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) {}, nil, pipeline.ErrTooManyErrors, "task inputs", }, - { - "invalid block", // name - "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", // contract - "", // from - "$(foo)", // data - "0", // evmChainID - "", // gas - "invalidblock", // block - nil, // specGasLimit - pipeline.NewVarsFrom(map[string]interface{}{ // vars - "foo": []byte("foo bar"), - }), - nil, // inputs - func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) {}, // setupClientMocks - nil, // expected - nil, // expectedErrorCause - "unsupported block param", // expectedErrorContains - }, { "missing chainID", "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", @@ -325,6 +240,48 @@ func TestETHCallTask(t *testing.T) { }, nil, nil, chains.ErrNoSuchChainID.Error(), }, + { + "simulate using latest block", + "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", + "", + "$(foo)", + "0", + "", + "latest", + nil, + pipeline.NewVarsFrom(map[string]interface{}{ + "foo": []byte("foo bar"), + }), + nil, + func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) { + contractAddr := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") + ethClient. + On("CallContract", mock.Anything, ethereum.CallMsg{To: &contractAddr, Gas: uint64(drJobTypeGasLimit), Data: []byte("foo bar")}, (*big.Int)(nil)). + Return([]byte("baz quux"), nil) + }, + []byte("baz quux"), nil, "", + }, + { + "simulate using pending block", + "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", + "", + "$(foo)", + "0", + "", + "pending", + nil, + pipeline.NewVarsFrom(map[string]interface{}{ + "foo": []byte("foo bar"), + }), + nil, + func(ethClient *evmclimocks.Client, config *pipelinemocks.Config) { + contractAddr := common.HexToAddress("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF") + ethClient. + On("PendingCallContract", mock.Anything, ethereum.CallMsg{To: &contractAddr, Gas: uint64(drJobTypeGasLimit), Data: []byte("foo bar")}). + Return([]byte("baz quux"), nil) + }, + []byte("baz quux"), nil, "", + }, } for _, test := range tests { From de2d7f9a13c827c6346871beb6fe7f9a78bd2b75 Mon Sep 17 00:00:00 2001 From: jinhoonbang Date: Wed, 14 Feb 2024 12:28:53 -0800 Subject: [PATCH 10/10] handle nil gas and gasPrice in backend test client for estimateGas --- .../evm/client/simulated_backend_client.go | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/core/chains/evm/client/simulated_backend_client.go b/core/chains/evm/client/simulated_backend_client.go index 3a235b8db40..c49637e7890 100644 --- a/core/chains/evm/client/simulated_backend_client.go +++ b/core/chains/evm/client/simulated_backend_client.go @@ -728,6 +728,28 @@ func toCallMsg(params map[string]interface{}) ethereum.CallMsg { callMsg.Value = value } + switch gas := params["gas"].(type) { + case nil: + // This parameter is not required so nil is acceptable + case uint64: + callMsg.Gas = gas + case hexutil.Uint64: + callMsg.Gas = uint64(gas) + default: + panic("unexpected type of 'gas' parameter; try hexutil.Uint64, or uint64") + } + + switch gasPrice := params["gasPrice"].(type) { + case nil: + // This parameter is not required so nil is acceptable + case *big.Int: + callMsg.GasPrice = gasPrice + case *hexutil.Big: + callMsg.GasPrice = gasPrice.ToInt() + default: + panic("unexpected type of 'gasPrice' parameter; try *big.Int, or *hexutil.Big") + } + return callMsg }