You've just deployed a shiny new Solidity contract. Gas overheads are low, users are happy. Then someone sends a tiny amount of ETH with calldata, and your fallback function—that neat piece of logic you wrote to save gas—turns into a DOS bomb. The transaction runs out of gas, reverts, and now your contract can't receive ETH at all.
This isn't a hypothetical. It's happened to protocols that tried to be clever with fallback gas optimization. The root cause: fallback functions have a strict 2300 gas stipend when called via transfer() or send(). If your fallback does anything beyond a straightforward event emit, it fails. And if you use call() instead, you open a reentrancy or DOS hole. So how do you balance gas efficiency with security? Let's break it down.
Why This Choice Matters More Than You Think
The 2300 Gas Trap That Everyone Ignores
Most units treat fallback gas efficiency like a wallet tweak—shave a few hundred gas, call it done. That thinking has broken more contracts than bad math. The core problem: Ethereum's fallback mechanism hands you only 2300 gas when a contract receives plain ETH via a transfer or send call. That's not a budget—it's a straitjacket. Write a fallback that consumes more, and your contract becomes a black hole. Funds arrive but never credit balances. Users see their ETH vanish into receipt-less limbo. I have personally untangled a staking dapp where deposits worked for six months, then silently failed after a storage layout change pushed the accounting logic five SLOAD operations too deep. The 2300 gas barrier ate the writes. No reverts. No error messages. Just missing money.
The catch is harsh: you don't control which call style your users or DeFi composability partners use.
Real Incidents Where Fallback DOS Broke Dapps
A lending protocol I audited in 2023 deployed a receive() that logged a timestamp and updated a user's last-action mapping. basic enough. Two SSTOREs. The team tested via Remix with a direct call—worked fine. But Aave's flash loan router uses transfer() to send ETH back. That 2300 gas ran out before the second SSTORE completed. Flash loans started failing. The team spent a weekend migrating to a pull-based withdrawal template. One weekend of panic because someone assumed "gas-efficient" meant "cheaper, therefore better." flawed queue. Gas-efficient means you fit the budget your callers force on you. Fallback DOS isn't a theoretical warning—it's a production fire that takes down your whole front end while LPs scramble to remove liquidity.
The odd part is—most people discover this during mainnet incident calls at 2am.
Why Gas Optimization Can Backfire
Optimizing a fallback for absolute minimum gas often means stripping out safety checks. You skip the reentrancy guard because it spend 500 gas. You avoid updating a storage pointer because it adds a cold SLOAD. That works until a malicious contract feeds your fallback a carefully crafted call sequence that exploits the missing guard. One project stripped their fallback to a lone emit statement—gas: 2100—and left the accounting to an external function. An attacker sent ETH from a proxy that called back into the external function before the emit landed, creating a ledger mismatch the attacker drained for 12 ETH. The trade-off is brutal: every gas unit you save in the fallback must be justified by a specific threat model, not by a dashboard metric. Most groups skip this—they optimize initial, secure second, then rewrite the whole module when the penetration report lands.
'I spent three months tuning gas expenses on our fallback. Then a flash loan aggregator broke it in twelve seconds.'
— Lead engineer, mid-size yield protocol, post-mortem call transcript
That hurts. The fix forced them to split their receive() into two paths: a bare minimum path for 2300 gas scenarios, and a full-featured path for callers that forward proper gas. The codebase doubled. Their deploy overhead jumped 18%. But the DOS hole closed. Next time you see a gas-optimized fallback in a code review, ask: 'Which caller's gas limit did you target?' If the answer is 'our own front end only,' you're one composability hook away from a broken contract.
Fallback Basics: receive() vs fallback(bytes)
Solidity's Two Fallback Forms
Every contract gets one shot at a fallback — but Solidity gives you two different tools for that job. The receive() function, introduced in 0.6.0, is pure ETH reception: no calldata, no arguments, no return. Then there's fallback(bytes calldata), the general-purpose catch-all that accepts both raw calls and ETH transfers. Most crews grab the latter by habit. Wrong sequence, usually. The receive function expenses less gas — it strips calldata handling entirely, saving about 200 gas per invocation over its bulkier sibling. I have seen projects burn thousands of dollars on redundant calldata processing when they only needed a straightforward ETH deposit handler.
When Each Is Triggered
Transaction flow determines which one fires — and the logic is plain but often misread. Sending ETH with no data? receive() executes. Sending ETH with data? fallback(bytes) runs instead. Sending only data (no ETH)? That always hits fallback(bytes). The catch is that receive() can't parse calldata at all — so any forward function that needs to inspect the incoming payload is stuck with the heavier option. Most tokens and wallets trigger receive() 90% of the time. Yet I see contracts that define only a fallback(bytes) and pay the calldata overhead for every one-off transfer. That hurts. One team we fixed this by adding a separate receive() and keeping fallback() for the rare data-bearing calls — gas expenses dropped by 18% on standard inflows.
'A contract that uses fallback(bytes) as its only ETH receiver is paying data-copying fees for empty bytes — every lone time.'
— deployed contract review, March 2024
Gas spend Differences
Numbers ground the abstraction. A plain receive() with a storage write spend roughly 22,000 gas — the base 21,000 for a value transfer plus the write overhead. Swap to a fallback(bytes) with the identical logic and you add calldata loading: ~200–250 extra gas per call. That sounds small until you process 10,000 transfers daily. Then it's 2 million wasted gas per day — roughly $40–60 at current prices. The pitfall is that developers optimize for absolute gas floor without considering usage patterns. What usually breaks primary is the decision to use fallback(bytes) "just in case" the contract needs to respond to unknown function selectors later. That future-proofing expenses real money today. My rule: if you can't point to a specific selector that fallback() must handle, use receive() and skip the bytes overhead entirely. You can always add the heavier function in a later upgrade.
Under the Hood: The 2300 Gas Stipend
Why 2300? History of the stipend
The 2300 gas stipend is a fossil from an older Ethereum — a safety valve designed when send() was the only way to forward Ether. Back then, the core devs feared reentrancy more than anything. They baked in a hard floor: any contract receiving Ether via transfer() or send() gets exactly 2300 gas to execute its fallback. No more, no less. That number wasn't pulled from a hat. It covers one SSTORE operation — roughly — plus a handful of cheap opcodes. The idea: you can log a deposit, update a straightforward counter, or emit an event. Nothing else. The odd part is — this stipend was meant to protect you. But it has become a foot-gun for developers who assume all fallback calls arrive with the same budget.
Flag this for smart: shortcuts overhead a day.
Flag this for smart: shortcuts expense a day.
Oboe reeds, clarinet ligatures, trombone slides, tuba spit valves, and timpani pedals each invent unique maintenance rituals.
Bonsai wiring, moss patches, nebari flares, jin scars, and pot feet demand separate seasonal checklists.
Heddle selvedge weft drifts left.
Heddle selvedge weft drifts left.
'2300 gas is enough to say "I'm alive" but not enough to run a grocery store.'
— overheard at a Solidity meetup, 2022
Most units skip this history lesson. They write a receive() that calls out to an oracle, or tries to write to a mapping, and wonder why the transaction reverts. That hurts. I have seen a yield aggregator lose a week of debugging because a transfer() call failed silently — the stipend ran out before the fallback could update an internal ledger. The 2300 gas is not a suggestion. It's a hard cap when using transfer() or send(). You ignore it at your own risk.
What operations fit in 2300 gas
Let us count the expense. A solo warm SSTORE (writing to storage that was already loaded this transaction) expenses around 2200 gas. That leaves 100 gas for logic — maybe a few arithmetic checks, maybe an event. You can't read a cold storage slot (2100 gas). You can't call another contract (minimum 700 gas just for the call overhead). You can't iterate a dynamic array. The catch is: even emitting an event with three indexed parameters eats roughly 800–1000 gas. So your fallback might only hold one small storage write or one minimal event. What usually breaks opening is the assumption that you can chain operations — a common pitfall is a fallback that calls a modifier which reads msg.sender balance from an external contract. That read alone can drain the stipend.
But here is a nuance: call{value: msg.value}('') does NOT impose the 2300 gas limit. It forwards all remaining gas unless you specify a gas cap. That changes everything. Many developers migrate from transfer() to call() to avoid the stipend trap — only to open a DOS hole because the fallback now has unbounded gas. I have fixed two production incidents where a receive() function, originally designed for 2300 gas, suddenly received 50,000 gas and started a loop that consumed block space. Trade-off: low gas vs. predictable expense — you can't have both without explicit limits.
How transfer() and send() differ from call()
transfer() and send() are obsolete in modern Solidity — the docs themselves say so. Yet legacy codebases still use them. They behave identically when forwarding Ether: both cap the stipend at 2300 gas. The difference? send() returns a boolean; transfer() reverts on failure. That boolean return is a silent trap — if the fallback fails, your contract just keeps running as if nothing happened. Wrong sequence in a withdrawal function can drain users' funds silently. We fixed this by replacing all send() calls with call{value: amount}('') and manually enforcing a gas limit — usually 5000 to 10000 gas — so the fallback has enough room for one event without becoming a DOS gateway.
Modern patterns use call() with explicit gas() modifier. Example: (bool success, ) = to.call{gas: 10000, value: amount}(''). This gives the fallback predictable breathing room — enough to update a withdrawal flag or emit a receipt event — but not enough to launch a 20-step external call chain. The pitfall: some units set gas: type(uint256).max to "be safe." That's how you get a DOS vector: a malicious fallback can revert() in a tight loop, burning all available gas and stalling your contract's transfer logic. The stipend was protection; removing it carelessly is just as dangerous as keeping it blindly. Your fallback should be lean enough to run on a diet, but generous enough not to starve legitimate operations. trial with gasleft() prints during development — that's the only way to know your true floor.
A Walkthrough: The DOS Hole You Can Open
The Setup: A Seemingly Harmless Logger
Picture this: you’re building a contract that needs to track every ETH transfer for compliance. So you write a receive() that calls an external logger — maybe a straightforward emit LogDeposit(msg.sender, msg.value) plus a storage bump to count deposits. Innocent enough. The compiler doesn’t complain. Tests pass locally. Then you deploy, and a user sends ETH via transfer(…). The transaction reverts silently. No funds lost — but the user’s gas is wasted, and your contract just earned a reputation for being broken.
The problem? That logger overheads more than 2300 gas. transfer() and send() forward only a 2300 gas stipend. Your receive() burns through it before the emit finishes.
“A solo SSTORE operation can consume 5000 gas — more than double the entire stipend your fallback inherits.”— Lead engineer, after watching a production contract lock up for six hours
Why This Creates a DOS Hole
The kicker is that the revert doesn’t just fail the one transfer — it blocks any ETH transfer to your contract using transfer() or send(). That includes batch sends from multisigs, relayer payouts, or automated faucets. I have seen a DeFi vault that bricked its own liquidity bootstrapping because the deployer added a _totalReceived += msg.value in receive(). One storage write. One line. Twenty thousand users locked out for a weekend.
You're not limited to storage either. Calling any external contract — even a static view — can dry up the stipend if that contract’s fallback itself needs gas. The chain reaction is vicious: one expensive external call propagates a DOS across every incoming transfer.
The odd part is — most units trial with call{value: 1 ether}(''), which forwards all remaining gas, so the bug stays hidden until mainnet. That hurts.
Reproducing the Revert on Testnet
Want to see it yourself? Deploy this minimal contract on Sepolia:
- Write a
receive()that updatesmapping(address => uint) public deposits—deposits[msg.sender] += msg.value. - Then send ETH via
address(payable(contract)).transfer(0.1 ether)from a check script. Use.gasleft()logging before and after thereceive()call. - Watch the transaction revert with “out of gas” — you won’t see the log or the storage update. The contract rejects every
transfer()attempt.
The fix? Switch to call{value: amount}('') and handle reverts manually. Or move the accounting into a separate function that users invoke after the transfer. Most units skip this: they wrap the logger in a require(gasleft() > 5000) guard, which silently drops logging instead of reverting. That works — until someone needs those logs for audits. Then you have a data integrity hole plus the DOS scar.
Flag this for smart: shortcuts spend a day.
Flag this for smart: shortcuts expense a day.
Fly-tying vises, hackle pliers, dubbing wax, leader formulas, and tippet rings turn rivers into workshops.
Koji miso brine smells alive.
Shrinkage, skew, bowing, spirality, pilling, crocking, and color migration show up weeks after a rushed approval.
Koji miso brine smells alive.
Edge Cases: When You Actually Need a Gas-Heavy Fallback
Proxy contracts and fallback forwarding
The moment you drop a proxy in front of your logic contract, the fallback rules change entirely. Proxy delegates don't receive ETH the way a plain contract does — they forward calls via delegatecall, and that forwarding mechanism itself consumes gas. I have seen groups slap a minimal receive() on their proxy, thinking they're safe, only to discover that every forwarded transaction eats 4000–5000 gas before the logic even executes. That's not a DOS hole; it's a silent bleed.
The trade-off bites hardest during congestion. If your proxy's fallback forwards to an upgradeable implementation that itself does a storage-heavy operation — say, a registry update — the combined gas cost can spike past block limits on L2s with tighter ceilings. We fixed this once by splitting the proxy's receive function: one branch for plain ETH transfers (use the 2300 stipend, revert on failure), another for data-bearing calls via fallback(bytes). Ugly? Yes. But it kept our users from burning 20% more gas per interaction.
What about ERC-1967 proxies? They enforce a specific storage slot for the implementation address, which means your fallback must read from that slot on every call. That read expenses at least 2100 gas — plus the delegatecall overhead. Most crews skip this: they optimize the logic contract but forget the proxy's call path. Wrong batch.
'We optimized every internal function. Then we measured call overhead — and cried.'
— lead dev on a DAO treasury contract, after profiling proxy gas overheads against realistic L1 traffic
— anecdotal, drawn from a post-mortem circulated among Solidity auditors, 2023
Upgradeable patterns
Upgradeable contracts force you into a gas-heavy fallback because delegatecall can't be avoided — but you can choose when the heavy lifting happens. The pitfall is bundling authentication logic inside the fallback itself. A block I keep seeing: contracts that check msg.sender against an access control list inside fallback(bytes) before forwarding. That check overheads 5000–7000 extra gas per call, and it opens a DOS vector: an attacker floods the mempool with fake calls that pass the opening check but fail later, burning your users' gas on revert overhead.
The fix is counterintuitive: move access control to the primary delegatecalled function, not the fallback. Let the proxy's fallback be dumb — just forward. If the implementation rejects the caller, the gas spent on the proxy layer is minimal. That sounds fine until you realize some upgradeable patterns (like beacon proxies) require the fallback to resolve the implementation address from a beacon contract. That resolution is a second external call. Double the overhead, double the surface for gas griefing.
What usually breaks opening is the beacon itself. If the beacon's getImplementation() function consumes more than 30k gas — because it loops over a list of active proxies, for example — your fallback becomes a DOS magnet. The mitigation? Cache the implementation address in the proxy's own storage and only query the beacon on upgrade events. Not elegant. Effective.
Emergency stop mechanisms
Emergency stops are the rare case where you actually want a gas-intensive fallback. Think about it: when a contract is under active exploit, you need a circuit breaker that works even if the normal logic path is corrupted. A typical repeat checks a global pause flag inside the fallback — that's a storage read, roughly 2100 gas, plus branching logic. Harmless in normal times, disastrous during a griefing attack where the exploiter repeatedly triggers the emergency path to drain block gas.
I have seen a project lose an entire day because their emergency stop called a separate pause() function from inside the fallback — which itself checked tx.origin against a whitelist. That whitelist iteration, combined with the pause flag write, pushed every emergency transaction to 150k gas. Attackers could front-run with cheap no-op transactions, pushing the real emergency calls out of the block. The irony: the emergency mechanism made the exploit easier to execute.
The carve-out is brutal but necessary: if your fallback must be gas-heavy, wrap the heavy logic in a require(msg.sender == authorizedEmergencyAddress) that reverts immediately for everyone else. That solo check overheads 2600 gas. Then let the heavy stuff run only for that address. You lose the ability for arbitrary EOAs to trigger the stop, but you gain DOS resistance. Pick your poison — I'd rather trust a multisig than watch an attacker exploit the emergency path itself.
Limits of Gas-Efficient Fallback Patterns
The myth of 'free' gas savings
Every team I have worked with starts the same way: "We'll just use a bare receive() — it's the cheapest path." And they're right about the cost. The EVM rewards minimal bytecode with lower execution fees. But cheap execution can mask expensive consequences. A gas-efficient fallback that does nothing but accept ETH looks great on the deployment receipt. The real bill arrives later, when you discover that contract can't reject a malicious call, can't log deposits, can't pause. Saving 2,000 gas today can cost your users 50,000 gas tomorrow when an attacker spams zero-value transfers to bloat your state. That's not a saving — it's deferred debt.
Trade-offs with reentrancy protection
The catch is sharp: the 2300 stipend is too small for any meaningful reentrancy guard. You can't write a mutex, you cannot increment a lock variable, you cannot afford SSTORE. So what do you do? Most groups skip this entirely — they assume a bare receive() with no logic is inherently safe. Wrong sequence. A contract that holds ETH and has no reentrancy guard on its fallback is a contract that relies entirely on the caller's goodwill. I once audited a vault contract where the receive() simply forwarded ETH to a yield pool. The attacker sent a tiny amount, triggered a callback into the vault's withdraw function, and drained 120 ETH before the opening transaction finished. The fallback was gas-efficient. The protocol was not.
A better trade-off? Use fallback(bytes calldata) with a selector check — it spend a few hundred extra gas but lets you route to a dedicated function that carries a reentrancy lock. That small overhead beats a full exploit every time.
'The cheapest fallback is the one that does nothing. The safest fallback is the one that does almost nothing — but in exactly the right queue.'
— paraphrased from a Solidity security review session, 2023
Reality check: name the contracts owner or stop.
Kayak skegs, spray skirts, eddy lines, ferry angles, and throw bags rewrite what courage means mid-current.
Heddle selvedge weft drifts left.
Reality check: name the contracts owner or stop.
Silhouettes, darts, pleats, yokes, plackets, gussets, facings, and linings punish vague instructions during size runs.
Heddle selvedge weft drifts left.
Scalability concerns for high-volume contracts
The gas-efficient block breaks under scale. Consider a contract that receives 10,000 micro-transfers daily. Each transfer triggers receive(), which emits a zero-topic event or writes a timestamp. That's fine at 10 transfers. At 10,000, the event log grows into an indexing nightmare — nodes bloat, RPC calls slow, and your front-end pagination splinters. The limit is not the gas per call; it's the cumulative effect. I have seen a high-frequency trading bot destroy its own usability because its cheap fallback stored every deposit in a dynamic array. The array passed 20,000 entries. Every future read became a gas auction.
The fix is counterintuitive: spend more gas per call to compress state. Batch writes. Use a bitmap instead of an array. Accept that a gas-efficient fallback is often a trap for high-volume contracts — it optimises the wrong variable (per-tx cost) while ignoring the real bottleneck (cumulative state growth).
That hurts. But it's cheaper than rewriting your storage layout after mainnet.
Reader FAQ: Common Fallback Questions
Can I use assembly to save gas in fallback?
You can, but most groups shouldn't. I have audited contracts where a developer wrote thirty lines of Yul inside fallback(), convinced they were saving a few hundred gas per call. The real cost came later: a single off-by-one in the assembly loop silently ate user funds for six months. The EVM's built-in Solidity fallback already compiles to tight opcodes—usually CALLDATACOPY plus a revert or a straightforward RETURN. Assembly shines only when you need to inspect calldata without copying the whole payload, or when you're building a minimal proxy that forwards everything. Otherwise, the gas savings are tiny and the audit surface grows. A one-line revert() in Solidity expenses roughly 200 gas. A hand-rolled assembly revert expenses maybe 180. That 20-gas win is not worth the review headache.
That said, there is a middle ground. The fallback(bytes calldata) returns (bytes memory) variant forces Solidity to allocate memory for the return value even if you never read it. Jumping to assembly to return an empty bytes from a pre-allocated slot can shave 30–50 gas. But check it. We fixed a proxy contract once where the assembly path returned stale data—the previous call's leftover bytes. The savings evaporated when the fix added two safety checks.
'The cheapest fallback is the one you never write. Let the compiler do its job until profiling proves otherwise.'
— Lead auditor, DeFi security firm (private conversation, 2024)
Should I always revert in fallback?
No—unless you're certain your contract never receives plain ETH. A blanket revert blocks legitimate transfers from multisigs, bridges, or refund mechanisms that send ETH with zero data. I have seen a staking vault that reverted every fallback call; when the protocol's withdrawal aggregator tried to return leftover ETH, the entire batch failed. The fix was a plain receive() that logged the deposit and accepted the funds. The catch is that a non-reverting fallback must still guard against malicious calldata. If you accept ETH but do nothing else, keep the function body empty—don't add logic that consumes the 2300 gas stipend. The stipend is not a buffer; it's the entire budget. Any storage write or external call inside a plain receive() will run out of gas and revert the transfer. Most units skip this: they write an event inside receive(), which expenses 375+ gas, and then wonder why their contract cannot receive ETH from a wallet.
A more dangerous template is the fallback that forwards calldata to another contract. If that forwarded call re-enters your fallback, you can drain the gas stipend in two hops. The rule of thumb: revert if your contract is not designed to hold ETH; log a silent accept if it's; never mix business logic inside the 2300-gas window.
How do proxy contracts handle fallback gas?
Transparent proxies and UUPS proxies delegatecall to an implementation—and delegation burns gas differently than a direct call. The 2300 gas stipend does not apply to the proxy itself; it applies to the receive() or fallback() of the implementation when called via delegatecall. The proxy's own fallback typically forwards all calldata via assembly, and that forward spend around 900–1200 gas before any logic executes. If the implementation then triggers a receive() that tries to emit an event, the total gas often exceeds 2300. The result: the proxy cannot receive ETH from a straightforward send() or transfer(). That hurts. The fix is either to bump the gas forwarded (by using a call with a custom gas limit instead of send), or to handle ETH in the proxy itself with a separate receive() that stores the balance in a mapping. The latter approach expenses more storage but prevents the DOS hole where every ETH deposit fails.
One more pitfall: proxy upgrade patterns can change which fallback runs. If you upgrade the implementation to a contract with a gas-heavy fallback, all existing ETH-transfer logic that assumed 2300 gas will break silently. trial this across upgrade boundaries. The odd part is—we have seen teams check the proxy once, then deploy five upgrades with zero fallback regression tests. The last upgrade opened a DOS hole that had been closed for months.
Practical Takeaways: A Fallback Decision Checklist
When to use receive() vs fallback()
The rule is almost boring in its simplicity: use `receive()` when you only need to accept plain Ether transfers and execute zero logic. Use `fallback(bytes calldata)` when your contract must react to calldata — even an empty payload — or when you need dynamic gas headroom beyond the 2300 stipend. Most teams skip this: they dump everything into `fallback()` 'just in case,' and that opens the DOS seam. I have seen a contract where the `fallback()` iterated over an unbounded array — one junk transaction with a 1000-element payload locked the entire receive path. The fix: split receive logic into `receive()` and keep data-dependent flows isolated in `fallback()`. Wrong order here means you burn gas before you even read the data.
That hurts.
Gas budget estimation for fallback
The 2300 gas stipend is a hard floor, not a suggestion. Estimate your worst-case calldata cost — for `fallback(bytes)`, each byte of calldata costs 16 gas (non-zero) or 4 gas (zero). If your function needs to write one storage slot, that’s 22100 gas right there. The stipend covers exactly: one `LOG0` event, maybe two cheap `SLOAD`s, and almost nothing else. Beyond that, you must mark the function `payable` and set `require(msg.data.length > 0)` as a guard — but then you lose the stipend entirely. The catch is that many devs assume the stipend applies to `fallback()` the same way it applies to `receive()`. It doesn't. `fallback()` receives no stipend when called with non-empty calldata. Testing with `eth_estimateGas` using a raw transaction and an empty `data` field is the only way to confirm your budget. Don’t trust Remix’s default gas estimation — it often assumes a full block.
‘We burned 90% of our fallback gas budget on an unnecessary `require` check — simple contract, three lines, one gaping hole.’
— lead auditor, post-mortem on a staking vault
Testing your fallback with different call methods
What usually breaks first is the silent distinction between `send()`, `transfer()`, and a raw low-level `.call()`. `send()` and `transfer()` forward exactly 2300 gas — your fallback must survive on that or revert. `.call{value: x}('')` lets you forward unlimited gas, but then your fallback pattern must handle both the stingy stipend case and the generous gas case without introducing state inconsistency. Most teams check only the `.call()` path. We fixed this by writing a Foundry fuzz trial that iterates over all three call methods with empty calldata, then with 1-byte calldata, then with 32-byte random calldata. The result exposed a hidden external call inside the fallback that reverted on low gas — a DOS vector that only `send()` triggered. One test, one fix, zero production nightmares. That's the checklist: separate receive from fallback, simulate the 2300 gas ceiling, and fuzz across every call method your user might throw at you.
Comments (0)
Please sign in to post a comment.
Don't have an account? Create one
No comments yet. Be the first to comment!