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
12 changes: 8 additions & 4 deletions core/scripts/common/vrf/jobs/jobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,17 @@ vrf [type=vrfv2
estimate_gas [type=estimategaslimit
to="%s"
multiplier="%f"
data="$(vrf.output)"]
data="$(vrf.output)"
block=%s]
jinhoonbang marked this conversation as resolved.
Show resolved Hide resolved
simulate [type=ethcall
from="%s"
to="%s"
gas="$(estimate_gas)"
gasPrice="$(jobSpec.maxGasPrice)"
extractRevertReason=true
contract="%s"
data="$(vrf.output)"]
data="$(vrf.output)"
block=%s]
jinhoonbang marked this conversation as resolved.
Show resolved Hide resolved
decode_log->vrf->estimate_gas->simulate
"""`

Expand Down Expand Up @@ -66,15 +68,17 @@ generate_proof [type=vrfv2plus
estimate_gas [type=estimategaslimit
to="%s"
multiplier="%f"
data="$(generate_proof.output)"]
data="$(generate_proof.output)"
block=%s]
jinhoonbang marked this conversation as resolved.
Show resolved Hide resolved
simulate_fulfillment [type=ethcall
from="%s"
to="%s"
gas="$(estimate_gas)"
gasPrice="$(jobSpec.maxGasPrice)"
extractRevertReason=true
contract="%s"
data="$(generate_proof.output)"]
data="$(generate_proof.output)"
block=%s]
jinhoonbang marked this conversation as resolved.
Show resolved Hide resolved
decode_log->generate_proof->estimate_gas->simulate_fulfillment
"""
`
Expand Down
10 changes: 10 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 All @@ -93,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()
Expand All @@ -103,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()
Expand Down Expand Up @@ -228,6 +234,7 @@ func main() {
*deployVRFOwner,
coordinatorJobSpecConfig,
*useTestCoordinator,
*simulationBlock,
)
case "v2plus":
coordinatorConfigV2Plus := v2plusscripts.CoordinatorConfigV2Plus{
Expand Down Expand Up @@ -257,9 +264,12 @@ func main() {
vrfKeyRegistrationConfig,
contractAddresses,
coordinatorConfigV2Plus,
*batchFulfillmentEnabled,
*nativeOnly,
nodesMap,
uint64(*maxGasPriceGwei),
coordinatorJobSpecConfig,
*simulationBlock,
)
}

Expand Down
11 changes: 11 additions & 0 deletions core/scripts/vrfv2/testnet/v2scripts/super_scripts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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:],
)
Expand Down Expand Up @@ -162,6 +167,7 @@ func DeployUniverseViaCLI(e helpers.Environment) {
*deployVRFOwner,
coordinatorJobSpecConfig,
*useTestCoordinator,
*simulationBlock,
)

vrfPrimaryNode := nodesMap[model.VRFPrimaryNodeName]
Expand All @@ -181,6 +187,7 @@ func VRFV2DeployUniverse(
deployVRFOwner bool,
coordinatorJobSpecConfig model.CoordinatorJobSpecConfig,
useTestCoordinator bool,
simulationBlock string,
) model.JobSpecs {
var compressedPkHex string
var keyHash common.Hash
Expand Down Expand Up @@ -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
Expand All @@ -355,6 +363,7 @@ func VRFV2DeployUniverse(
}(),
contractAddresses.CoordinatorAddress,
contractAddresses.CoordinatorAddress,
simulationBlock,
)
if deployVRFOwner {
formattedVrfPrimaryJobSpec = strings.Replace(formattedVrfPrimaryJobSpec,
Expand All @@ -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
Expand All @@ -386,6 +396,7 @@ func VRFV2DeployUniverse(
}(),
contractAddresses.CoordinatorAddress,
contractAddresses.CoordinatorAddress,
simulationBlock,
)
if deployVRFOwner {
formattedVrfBackupJobSpec = strings.Replace(formattedVrfBackupJobSpec,
Expand Down
Loading
Loading