Lab: pass every value cap and lose everything
Send a transaction worth nothing, then watch it move funds you never approved again.
The signing lesson listed unlimited ERC-20 approvals among the things a value cap does not catch. This is that in fifteen lines, and the reason it deserves a lab is that the dangerous transaction looks like the safest one you will sign all day.
- 1
Model an allowance and a guardrail that caps the value moved by any single transaction.
bashcat > /tmp/appr.py <<'EOF' MAX_UINT = 2**256 - 1 allowance = {} MAX_TX_VALUE = 100 # the guardrail def submit(name, value, effect): if value > MAX_TX_VALUE: return f" {name}: DENIED (value {value} over cap {MAX_TX_VALUE})" effect() return f" {name}: ALLOWED (value {value})" print(submit("approve(0xDex, MAX_UINT)", 0, lambda: allowance.__setitem__("0xDex", MAX_UINT))) def transfer_from(spender, amount): if allowance.get(spender, 0) < amount: return f" DENIED (allowance {allowance.get(spender, 0)})" allowance[spender] -= amount return f" moved {amount:,}" print("\nlater, with no further approval from you and no new signature:") print(transfer_from("0xDex", 1_000_000)) print(transfer_from("0xDex", 5_000_000_000)) EOF python3 /tmp/appr.py - 2
The approval carried zero value, so every value-based control waved it through.
textapprove(0xDex, MAX_UINT): ALLOWED (value 0) later, with no further approval from you and no new signature: moved 1,000,000 moved 5,000,000,000 - 3
Clean up.
bashrm /tmp/appr.py
A per-transaction value cap asks how much this transaction moves. An approval moves nothing; it grants standing authority for someone else to move funds later, without another signature from you and without another transaction for your guardrails to inspect.
- The dangerous parameter is the allowance amount, which is data inside the call rather than the transaction's value. A guardrail that only reads value will never see it.
- MAX_UINT is the default in a great deal of tooling, because it saves users a second approval later. Convenience is why it is everywhere.
- Revoking means sending another transaction to set the allowance to zero, and until you do, the grant survives the agent being deleted, its keys being rotated and its policies being revoked.
This is why the extended guardrails call out unlimited approval blocking specifically. It is not a variation of a value cap, it is a different check reading a different field, and the transaction it catches is the one that looks harmless.
When reviewing what an agent may sign, ask what each call grants as well as what it moves. Approvals, permits, delegations and setApprovalForAll all move nothing and give away everything.
Check your understanding
3 questionsWhy did the value cap allow the approval?
What survives deleting the agent and rotating its keys?
What question catches this class of transaction?