ApeWorX / ApeWorX/web3.py

`Wei` and `Gwei` lose their type through arithmetic — `+=` / `-=` rejected by mypy

Open
#3,833 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
5.5k
Forks
1.7k
Avg merge
3d 10h
Merged PRs (30d)
2

Description

### What feature should we add?

## Problem

`Wei` and `Gwei` are currently defined as `NewType` aliases over `int`:

```python
Wei = NewType("Wei", int)
Gwei = NewType("Gwei", int)
```

`NewType` creates a distinct type for the type checker, but all arithmetic operations return the *base* type (`int`), not the `NewType`. This means any arithmetic on `Wei` or `Gwei` values immediately "escapes" the type and mypy flags the result as `int`, not `Wei`/`Gwei`.

In practice, augmented assignment (`+=`, `-=`) is rejected outright:

```python
x = Wei(5)
x += Wei(1) # error: Incompatible types in assignment (expression has type "int", variable has type "Wei")
```

Even plain addition requires an explicit cast every time:

```python
total: Wei = Wei(0)
for tx in block["transactions"]:
total = Wei(total + tx["value"]) # cast needed on every accumulation
```

This affects real-world usage patterns — accumulating gas costs, summing balances, computing fee estimates — and makes well-typed Ethereum code significantly more verbose.

## Proposed fix

Replace the `NewType` aliases with `int` subclasses that override arithmetic operators to preserve the return type:

```python
class Wei(int):
def __add__(self, other: "Wei") -> "Wei": ...
def __sub__(self, other: "Wei") -> "Wei": ...
def __mul__(self, other: int) -> "Wei": ...
# etc.

class Gwei(int):
...
```

Because the classes subclass `int`, they are fully backward-compatible: anywhere `int` is accepted, `Wei`/`Gwei` still work. The `isinstance` check also becomes meaningful, which opens the door for formatters to return typed values instead of plain `int`.

As a natural follow-up, the result formatters in `method_formatters.py` can wrap monetary fields (`gasPrice`, `value`, `maxFeePerGas`, `effectiveGasPrice`, `baseFeePerGas`, `eth_getBalance`, etc.) with `Wei(...)` and withdrawal `amount` with `Gwei(...)`, so that values coming out of RPC calls already carry the correct type at the boundary.

## Backward compatibility

- `Wei` and `Gwei` are `int` subclasses, so all existing code that treats them as `int` continues to work without modification.
- `NewType` values cannot be used with `isinstance`; the new classes can — this is strictly additive.
- The formatter changes are internal and do not affect the public API.

## Prior art

The `eth_typing` library uses a similar pattern for `BlockNumber` (also a `NewType`), and the same limitation applies there. This proposal is scoped to `Wei` and `Gwei` since those are most commonly involved in arithmetic.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.