Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

VRF zero confirmation delay #11947

Merged
merged 12 commits into from
Feb 15, 2024
Merged
76 changes: 72 additions & 4 deletions core/chains/evm/client/simulated_backend_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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))
jinhoonbang marked this conversation as resolved.
Show resolved Hide resolved
}

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))
jinhoonbang marked this conversation as resolved.
Show resolved Hide resolved
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 {
Expand Down Expand Up @@ -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))
Expand All @@ -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
Expand All @@ -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) {
Copy link
Contributor

Choose a reason for hiding this comment

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

why is "input" and "data" different for eth_estimateGas vs eth_call?

Copy link
Contributor Author

@jinhoonbang jinhoonbang Feb 14, 2024

Choose a reason for hiding this comment

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

according to the docs, "input" is used for both eth_estimateGas and eth_call. This PR just adds support for "input".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"data" is used for eth_sign and eth_signSignature

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
Expand All @@ -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")
Expand All @@ -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)
}
}
70 changes: 56 additions & 14 deletions core/internal/cltest/contract_mock_receiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ 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)
Expand All @@ -55,7 +55,7 @@ func (receiver contractMockReceiver) MockResponse(funcName string, responseArgs
Return(encoded, nil)
}

func (receiver contractMockReceiver) MockMatchedResponse(funcName string, matcher func(callArgs ethereum.CallMsg) bool, responseArgs ...interface{}) *mock.Call {
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)
Expand All @@ -65,33 +65,75 @@ func (receiver contractMockReceiver) MockMatchedResponse(funcName string, matche

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
}),
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 {
func (receiver contractMockReceiver) MockCallContextMatchedResponse(funcName string, matcher func(args map[string]interface{}) bool, responseArgs ...interface{}) *mock.Call {
jinhoonbang marked this conversation as resolved.
Show resolved Hide resolved
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
jinhoonbang marked this conversation as resolved.
Show resolved Hide resolved

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 &&
matcher(args)
}),
mock.Anything).
Return(nil, errors.New("revert"))
Return(nil).Run(func(args mock.Arguments) {
resp := args.Get(1).(*hexutil.Bytes)
*resp = encoded
})
}

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)
}

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(errors.New("revert"))

}

func (receiver contractMockReceiver) mustEncodeResponse(funcName string, responseArgs ...interface{}) []byte {
Expand Down
3 changes: 3 additions & 0 deletions core/scripts/common/vrf/setup-envs/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -257,6 +258,8 @@ func main() {
vrfKeyRegistrationConfig,
contractAddresses,
coordinatorConfigV2Plus,
*batchFulfillmentEnabled,
*nativeOnly,
nodesMap,
uint64(*maxGasPriceGwei),
coordinatorJobSpecConfig,
Expand Down
18 changes: 16 additions & 2 deletions core/scripts/vrfv2plus/testnet/v2plusscripts/super_scripts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -569,6 +579,8 @@ func DeployUniverseViaCLI(e helpers.Environment) {
vrfKeyRegistrationConfig,
contractAddresses,
coordinatorConfig,
*batchFulfillmentEnabled,
*nativeOnly,
nodesMap,
uint64(*gasLaneMaxGas),
coordinatorJobSpecConfig,
Expand All @@ -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,
Expand Down Expand Up @@ -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()
}
Expand Down
10 changes: 6 additions & 4 deletions core/scripts/vrfv2plus/testnet/v2plusscripts/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
22 changes: 11 additions & 11 deletions core/services/keeper/registry1_1_synchronizer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Loading
Loading