diff --git a/core/state_transition.go b/core/state_transition.go index 52375bedaa..8cea986f81 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -607,7 +607,15 @@ func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorizatio // Validate signature values and recover authority. authority, err = auth.Authority() if err != nil { - return authority, fmt.Errorf("%w: %v", ErrAuthorizationInvalidSignature, err) + // In simulation mode (e.g. eth_estimateGas, eth_call), allow + // authorizations with invalid signatures by assuming the + // transaction sender is the authority. This lets callers estimate + // gas for EIP-7702 transactions before signing the authorization. + if st.msg.SkipTransactionChecks { + authority = st.msg.From + } else { + return authority, fmt.Errorf("%w: %v", ErrAuthorizationInvalidSignature, err) + } } // Check the authority account // 1) doesn't have code or has exisiting delegation @@ -619,7 +627,9 @@ func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorizatio if _, ok := types.ParseDelegation(code); len(code) != 0 && !ok { return authority, ErrAuthorizationDestinationHasCode } - if have := st.state.GetNonce(authority); have != auth.Nonce { + // Skip nonce validation in simulation mode, consistent with the + // top-level SkipNonceChecks behaviour for the sender account. + if have := st.state.GetNonce(authority); have != auth.Nonce && !st.msg.SkipNonceChecks { return authority, ErrAuthorizationNonceMismatch } return authority, nil diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 62e9979d3d..77df0008bd 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -972,6 +972,23 @@ func TestEstimateGas(t *testing.T) { }, expectErr: core.ErrSetCodeTxCreate, }, + // EstimateGas with unsigned (zero-signature) EIP-7702 authorization + // should succeed, using the sender as the authority. This allows + // callers to estimate gas before signing the authorization. + { + blockNumber: rpc.LatestBlockNumber, + call: TransactionArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(0)), + AuthorizationList: []types.SetCodeAuthorization{{ + ChainID: *uint256.NewInt(0), + Address: accounts[0].addr, + Nonce: uint64(genBlocks + 1), + }}, + }, + want: 46000, + }, } for i, tc := range testSuite { result, err := api.EstimateGas(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides)