Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changelog/estimate-gas-batch-calls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
pytempo: patch
---

Fixed `TempoTransaction.to_estimate_gas_request` estimating only the first call of a batch. A transaction with several calls is now sent as `calls`, so the node estimates the whole batch. Previously the returned gas limit covered only the first call, and the node rejected the resulting batch transaction with "intrinsic gas too low". Single-call requests are unchanged.
37 changes: 21 additions & 16 deletions pytempo/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,9 @@ def to_estimate_gas_request(
) -> dict:
"""Build an eth_estimateGas request dict from this transaction.

A single call is sent as ``to``/``data``/``value``. A batch is sent as
``calls`` so the node estimates every call, not just the first.

Args:
sender: Address of the sender (hex string).
key_id: Optional access key address for keychain signature gas estimation.
Expand All @@ -571,23 +574,25 @@ def to_estimate_gas_request(
if not self.calls:
raise ValueError("Transaction must have at least one call")

first_call = self.calls[0]
to_addr = bytes(first_call.to)
data = first_call.data
value = first_call.value

data_hex = "0x" + data.hex() if data else "0x"

request: dict = {
"from": sender,
"data": data_hex,
}
request: dict = {"from": sender}

if to_addr:
request["to"] = to_checksum_address(to_addr)

if value:
request["value"] = hex(value)
if len(self.calls) == 1:
call = self.calls[0]
to_addr = bytes(call.to)
request["data"] = "0x" + call.data.hex() if call.data else "0x"
if to_addr:
request["to"] = to_checksum_address(to_addr)
if call.value:
request["value"] = hex(call.value)
else:
request["calls"] = [
{
"to": to_checksum_address(bytes(c.to)) if bytes(c.to) else None,
"value": hex(c.value),
"data": "0x" + c.data.hex(),
}
for c in self.calls
]

if key_id is not None:
request["keyId"] = key_id
Expand Down
44 changes: 43 additions & 1 deletion tests/test_typed_models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Tests for the strongly-typed models."""

import pytest
from eth_utils import keccak
from eth_utils import keccak, to_checksum_address

from pytempo import (
AccessListItem,
Expand Down Expand Up @@ -171,6 +171,48 @@ def test_validate_fee_mismatch(self):
with pytest.raises(ValueError, match="cannot exceed"):
tx.validate()

def test_estimate_gas_request_single_call(self):
tx = TempoTransaction(
chain_id=1,
gas_limit=21000,
calls=(Call.create(to="0x" + "a" * 40, value=5, data="0x1234"),),
)
request = tx.to_estimate_gas_request(sender="0x" + "b" * 40)
assert request == {
"from": "0x" + "b" * 40,
"data": "0x1234",
"to": to_checksum_address("0x" + "a" * 40),
"value": "0x5",
}

def test_estimate_gas_request_includes_every_call_in_a_batch(self):
tx = TempoTransaction(
chain_id=1,
gas_limit=21000,
calls=(
Call.create(to="0x" + "a" * 40, value=5, data="0x1234"),
Call.create(to="0x" + "c" * 40),
Call.create(to=b"", data="0x6000"),
),
)
request = tx.to_estimate_gas_request(sender="0x" + "b" * 40)
assert request == {
"from": "0x" + "b" * 40,
"calls": [
{
"to": to_checksum_address("0x" + "a" * 40),
"value": "0x5",
"data": "0x1234",
},
{
"to": to_checksum_address("0x" + "c" * 40),
"value": "0x0",
"data": "0x",
},
{"to": None, "value": "0x0", "data": "0x6000"},
],
}

def test_sign_returns_new_transaction(self):
tx = TempoTransaction(
chain_id=42429,
Expand Down