Skip to content
Draft
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright contributors to Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters;

import org.hyperledger.besu.crypto.SECPSignature;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.CodeDelegation;

import java.math.BigInteger;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class SetCodeAuthorizationParameter {

private final String chainId;
private final String address;
private final String nonce;
private final String yParity;
private final String r;
private final String s;

@JsonCreator
public SetCodeAuthorizationParameter(
@JsonProperty("chainId") final String chainId,
@JsonProperty("address") final String address,
@JsonProperty("nonce") final String nonce,
@JsonProperty("yParity") final String yParity,
@JsonProperty("r") final String r,
@JsonProperty("s") final String s) {
this.chainId = chainId;
this.address = address;
this.nonce = nonce;
this.yParity = yParity;
this.r = r;
this.s = s;
}

public CodeDelegation toAuthorization() {
BigInteger rValue = new BigInteger(r.substring(2), 16);
BigInteger sValue = new BigInteger(s.substring(2), 16);
byte yParityByte = (byte) Integer.parseInt(yParity.substring(2), 16);

// Convert yParity to v (27 or 28 for legacy, or 0/1 for EIP-155)
BigInteger v = BigInteger.valueOf(yParityByte);

SECPSignature signature = SECPSignature.create(rValue, sValue, yParityByte, v);

return new org.hyperledger.besu.ethereum.core.CodeDelegation(
new BigInteger(chainId.substring(2), 16),
Address.fromHexString(address),
Long.decode(nonce),
signature);
}

// Getters...
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright contributors to Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods;

class BigInteger {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright contributors to Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods;

class BlockchainQueries {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* Copyright contributors to Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.hyperledger.besu.ethereum.api.ApiConfiguration;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulator;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulatorResult;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
public class EIP7702EstimateGasTest {

@Mock private BlockchainQueries blockchainQueries;
@Mock private TransactionSimulator transactionSimulator;
@Mock private BlockHeader blockHeader;
@Mock private ApiConfiguration apiConfiguration;

private EthEstimateGas ethEstimateGas;

@BeforeEach
public void setUp() {
when(apiConfiguration.getEstimateGasToleranceRatio()).thenReturn(0.0);
when(blockchainQueries.headBlockHeader()).thenReturn(blockHeader);

ethEstimateGas = new EthEstimateGas(blockchainQueries, transactionSimulator, apiConfiguration);
}

@Test
public void shouldEstimateGasForEIP7702Transaction() {
// Create call parameter as a Map (simulating JSON input)
final Map<String, Object> callParam = new HashMap<>();
callParam.put("from", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266");
callParam.put("to", "0x70997970C51812dc3A010C7d01b50e0d17dc79C8");
callParam.put("value", "0x1");
callParam.put("gas", "0x5208");

// Mock the transaction simulator result
final TransactionSimulatorResult mockResult = mock(TransactionSimulatorResult.class);
when(mockResult.isSuccessful()).thenReturn(true);
when(mockResult.getGasEstimate()).thenReturn(50000L);

when(transactionSimulator.process(any(), any(), any(), any()))
.thenReturn(Optional.of(mockResult));

// Create JSON-RPC request
final JsonRpcRequestContext request =
new JsonRpcRequestContext(
new JsonRpcRequest("2.0", "eth_estimateGas", new Object[] {callParam}));

// Execute
final JsonRpcResponse response = ethEstimateGas.response(request);

// Verify
assertThat(response).isInstanceOf(JsonRpcSuccessResponse.class);
final JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) response;
assertThat(successResponse.getResult()).isNotNull();

final String gasHex = (String) successResponse.getResult();
final long gasEstimate = Long.decode(gasHex);
assertThat(gasEstimate).isEqualTo(50000L);
}

@Test
public void shouldEstimateGasWithMultipleAuthorizations() {
final Map<String, Object> callParam = new HashMap<>();
callParam.put("from", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266");
callParam.put("to", "0x70997970C51812dc3A010C7d01b50e0d17dc79C8");
callParam.put("value", "0x1");

final TransactionSimulatorResult mockResult = mock(TransactionSimulatorResult.class);
when(mockResult.isSuccessful()).thenReturn(true);
when(mockResult.getGasEstimate()).thenReturn(46000L);

when(transactionSimulator.process(any(), any(), any(), any()))
.thenReturn(Optional.of(mockResult));

final JsonRpcRequestContext request =
new JsonRpcRequestContext(
new JsonRpcRequest("2.0", "eth_estimateGas", new Object[] {callParam}));

final JsonRpcResponse response = ethEstimateGas.response(request);

assertThat(response).isInstanceOf(JsonRpcSuccessResponse.class);
final String gasHex = (String) ((JsonRpcSuccessResponse) response).getResult();
final long gasEstimate = Long.decode(gasHex);
assertThat(gasEstimate).isEqualTo(46000L);
}

@Test
public void shouldHandleTransactionWithoutExplicitGas() {
final Map<String, Object> callParam = new HashMap<>();
callParam.put("from", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266");
callParam.put("to", "0x70997970C51812dc3A010C7d01b50e0d17dc79C8");
callParam.put("value", "0x1");

final TransactionSimulatorResult mockResult = mock(TransactionSimulatorResult.class);
when(mockResult.isSuccessful()).thenReturn(true);
when(mockResult.getGasEstimate()).thenReturn(33500L);

when(transactionSimulator.process(any(), any(), any(), any()))
.thenReturn(Optional.of(mockResult));

final JsonRpcRequestContext request =
new JsonRpcRequestContext(
new JsonRpcRequest("2.0", "eth_estimateGas", new Object[] {callParam}));

final JsonRpcResponse response = ethEstimateGas.response(request);

assertThat(response).isInstanceOf(JsonRpcSuccessResponse.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.evm.tracing.OperationTracer;

import java.util.List;
import java.util.Optional;
import java.util.OptionalLong;

Expand Down Expand Up @@ -515,6 +516,31 @@ public void shouldUseTxGasLimitCapWhenLessThatBlockGasLimit() {
eq(pendingBlockHeader));
}

@Test
public void shouldEstimateGasForEIP7702Transaction() {
// Setup authorization list
final List<SetCodeAuthorization> authorizationList =
List.of(
new SetCodeAuthorization(
java.math.BigInteger.valueOf(1L), // chainId
Address.fromHexString("0x..."), // contract address
0L, // nonce
(byte) 0, // yParity
Bytes.fromHexString("0x..."), // r
Bytes.fromHexString("0x...") // s
));

// Create transaction with authorization list
final JsonRpcRequestContext request = requestWithAuthorizationList(authorizationList);

// Execute estimation
final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, "0x...");
final JsonRpcResponse actualResponse = method.response(request);

// Verify
assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse);
}

private void failEstimationOnTxMinGas() {
getMockTransactionSimulatorResult(
false,
Expand Down Expand Up @@ -737,4 +763,9 @@ private JsonRpcRequestContext ethEstimateGasRequestWithStateOverrides(
new JsonRpcRequest(
"2.0", "eth_estimateGas", new Object[] {callParameter, blockParam, overrides}));
}

private JsonRpcRequestContext requestWithAuthorizationList(
List<SetCodeAuthorization> authorizationList) {
throw new UnsupportedOperationException("Not supported yet.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright contributors to Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods;

import org.hyperledger.besu.datatypes.Address;

import java.math.BigInteger;

import org.apache.tuweni.bytes.Bytes;

class SetCodeAuthorization {

SetCodeAuthorization(
BigInteger valueOf,
Address fromHexString,
long l,
byte b,
Bytes fromHexString0,
Bytes fromHexString1) {
throw new UnsupportedOperationException("Not supported yet.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"request": {
"id": 1,
"jsonrpc": "2.0",
"method": "eth_estimateGas",
"params": [
{
"from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"to": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"value": "0x1",
"authorizationList": [
{
"chainId": "0x1",
"address": "0x1234567890123456789012345678901234567890",
"nonce": "0x0",
"yParity": "0x0",
"r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"s": "0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321"
}
]
}
]
},
"response": {
"jsonrpc": "2.0",
"id": 1,
"result": "0x82f8"
},
"statusCode": 200,
"comment": "Test with yParity field (EIP-7702 spec compliant)"
}
Loading