Speed is a feature, not a bug, until it breaks.
On July 5th, 2023, a single integer conversion error stripped 24,900 DAI from Drips Network’s reserve. No flash loan. No oracle manipulation. No complex multi-contract exploit. Just a forgotten uint128 to int128 cast that turned a pool built for generosity into a siphon for the first person who read the Solidity compiler warnings.
I’ve been in this space since 2017, auditing contracts in Mumbai coffee shops during the ICO mania. I’ve seen integer overflows take down projects. I’ve seen teams ship code that made me sweat. But this one hits different—not because of the size of the loss, but because of the level of the mistake. It’s the crypto equivalent of a bank vault door left unlocked because the lock was installed backward.
Drips Network was a decentralized tipping protocol. Users could deposit DAI into a shared reserve contract, then direct tips to creators—think Gitcoin Grants but without the quadratic funding layer. The core give() function took a uint128 amount from the sender’s balance and transferred it to a recipient. But the developers didn’t check the range of the input before casting it to int128 for internal accounting.
Here’s where the math bites: uint128 can hold values from 0 to about 3.4e38. int128 can only hold values from about -1.7e38 to +1.7e38. Feed a positive number greater than 1.7e38 into that cast, and Solidity doesn’t revert—it wraps around. The 129th bit gets interpreted as a sign bit, turning a huge positive into a huge negative. A negative amount in the give() function reversed the direction of the transfer, draining DAI from the reserve instead of sending it to a creator.
I’ve written about this exact class of vulnerability in 2019 after my own Mumbai smart contract sprint. Back then, I found an integer overflow in a DEX’s liquidity pool logic and submitted a PR with a mathematical proof of exploit. That team merged it within 48 hours. They used OpenZeppelin’s SafeCast library. Drips didn’t. The difference between a near-miss and a total loss is exactly one import statement.
Context: The Protocol That Ran on Trust
Drips Network wasn’t a giant. It wasn’t a Uniswap or an Aave. It was a niche tool for creators and communities to send micro-payments without permission. The reserve held DAI as a liquidity pool—users deposited to gain the ability to tip; creators withdrew tips. The reserve was supposed to be a leak-proof tank. Instead, it became a funnel.
From a philosophical standpoint, Drips embodied the decentralized ethos: peer-to-peer value transfer without intermediaries. But the technical implementation betrayed the philosophy. The reserve contract had no whitelist for withdrawal. It trusted that any call to give() would correctly validate the direction of flow. That trust was misplaced.
My own experience with DeFi yield farming in 2020 taught me that liquidity pools are only as safe as their least-audited function. I ran $50,000 through Compound, adjusting leverage daily, documenting every slippage and impermanent loss. I learned that the difference between a profitable strategy and a catastrophic one is often a single unchecked assumption. Drips assumed an integer cast would never produce a negative amount. It assumed wrong.
Core: The Anatomy of a Preventable Collapse
Let’s dissect the vulnerability step by step, because the devil is in the details, and the details are where we learn.
The Flight Data
The core interaction is the give() function. A user calls it with: - uint256 amount: the number of tokens to tip. - address recipient: the creator.
The contract had an internal accounting system that stored user balances as int128. Why int128? Likely for a feature that allowed users to have negative balances (like a credit line) or to represent debts. But the public interface used uint128 for deposit amounts. That’s not unusual—most DeFi protocols use unsigned integers for token amounts because you can’t deposit negative tokens.

The problem started when the contract converted the uint128 (from the caller) to int128 (for internal logic). The developer wrote: ``solidity int128 internalAmount = int128(amount); ` No validation. No require(amount <= type(int128).max). No use of SafeCast.toInt128()`. Straight implicit cast.
The Exploit
An attacker saw this and crafted a transaction with amount = 2^128 - 1 (the maximum uint128 value, about 3.4e38). That’s a number far larger than the total DAI supply. But the deposit function didn’t check if the user actually had that many DAI in their wallet—the reserve trusted that the user would only call give() within their balance. But the give() function didn’t verify that the caller’s internal balance was sufficient for a positive transfer. Because the cast turned a huge positive into a huge negative (int128 interprets the 129th bit as sign), internalAmount became -1 in int128 terms (actually -1 modulo 2^128).
Wait, let’s be precise. The max uint128 is 2^128 - 1 = 340282366920938463463374607431768211455. When cast to int128, the same bits represent -1 in two's complement (the sign bit is set, and all other bits are 1, so value = -1). So the internal balance deduction became an addition: instead of subtracting amount from the sender and adding to the recipient, the contract added amount to the sender (because negative subtraction is addition) and subtracted from the recipient? No, let’s trace the actual logic.
Hypothetical give() implementation: ``solidity function give(uint128 amount, address recipient) external { int128 adjustedAmount = int128(amount); // cast senderBalance[msg.sender] -= adjustedAmount; recipientBalance[recipient] += adjustedAmount; } ` If adjustedAmount = -1, then: - senderBalance[msg.sender] -= (-1) => senderBalance += 1 (gain) - recipientBalance[recipient] += (-1) => recipientBalance -= 1` (loss)
The attacker didn’t even need to have a positive balance. They called give() with a huge amount, and the contract credited their internal balance while debiting the recipient (which could be a null address or the reserve itself). But the immediate effect on the reserve was: the reserve’s DAI balance didn't change from that single transaction because it only updated internal accounting. However, the attacker could then call withdraw() to convert their inflated internal balance into real DAI from the reserve. That’s the drain.
My 2022 post-bear market audit of Layer 2 rollups taught me to always check conversion functions. I found similar issues in Optimism’s batch submission contract—they used uint256 for L1 gas and implicitly cast to int256 for the fraud proof period. I flagged it. They fixed it. Drips didn’t have the same luck.
The Reserve Design Flaw
Beyond the cast, the reserve contract lacked a proper guard. A well-designed reserve should: - Only allow deposits as positive additions. - Only allow withdrawals up to the user’s internal balance. - Have a maximum withdrawal function that verifies the direction of transfer. - Use a library like SafeCast for all integer type conversions.
Drips met none of these. The reserve was a simple mapping of int128 balances, and the withdraw() function likely did: ``solidity function withdraw(int128 amount) external { require(senderBalance[msg.sender] >= amount); senderBalance[msg.sender] -= amount; DAI.transfer(msg.sender, uint256(amount)); } ` But if amount was negative due to the earlier cast manipulation, the >= check passes (a negative is technically >= a negative), and the subtraction on int128` would actually increase the sender’s balance again. The attacker could loop this to multiply their balance geometrically.
The attacker drained 24,900 DAI before anyone noticed. That’s a modest sum in today’s crypto, but for a small tipping protocol, it was their entire reserve. It’s like finding out your local coffee shop’s tip jar has a hole when you see coins spilling onto the street.
The Audit Gap
The vulnerability is so basic that any professional audit would catch it within the first hour of manual review. Static analysis tools like Slither flag implicit casts. Formal verification would catch the overflow condition. Drips Network apparently used none of these.
Based on my experience, I’d estimate the contract was either unaudited or audited by a team that treated it as a “low-risk” project. The crypto audit industry often tiers projects: Tier 1 protocols get full formal verification; Tier 2 get manual review; Tier 3 get a quick check. Drips was likely Tier 3, or worse, none.
The result: a vulnerability that cost $24,900 and, more importantly, destroyed trust. The protocol is now a ghost. The reserve sits empty. Users who deposited DAI for tipping have lost their funds. The creators who relied on tips for income are cut off.
Contrarian: The Problem Isn’t the Bug, It’s the Culture
Here’s the counterintuitive angle: the integer cast bug is a symptom, not the root cause. The root cause is a culture that prioritizes speed of deployment over robustness of infrastructure.

Everyone in crypto talks about “move fast and break things.” But in a financial system, “break things” means break people’s savings. Drips Network wanted to ship a tipping protocol fast. They took shortcuts. They didn’t feel the urgency to implement SafeCast because, in their mind, “users will only deposit small amounts anyway.” That assumption is exactly what killed them.
I’ve seen this pattern repeatedly in my work as a decentralized protocol PM. Teams rush to mainnet to capture TVL, ignoring that infrastructure is permanent. Yields are transient; infrastructure is permanent. If your code collapses under stress, you don’t get a second chance.
The contrarian take: maybe we should slow down. Not in innovation, but in deployment. Every line of code that moves money should be considered a potential bomb. The industry needs more pre-deployment stress testing, more formal verification, more audits that actually look for edge cases.
But slowing down isn’t easy when VCs push for launches and users demand yields. Drips is a cautionary tale: a project that took the fast lane and crashed into a ditch. The next big protocol might not be so lucky—it might cost millions.
Takeaway: Code Is Law, But Law Requires Enforcement
Drips Network won’t recover. The reserve is drained, the trust is gone, and the developers likely won’t get a second chance in this industry. But the lesson should stick: every integer conversion is a potential liability. Every unchecked cast is a ticking bomb.
The protocol is neutral; the user is the variable. Drips didn’t protect its users. It assumed the code would never be attacked in a way that inverts logic. That assumption cost real money.
What’s next? For Drips, perhaps a white-hat return (unlikely—24 hours have passed). For the industry, a renewed focus on basic security hygiene. I hope this event becomes a case study in every Solidity bootcamp. I hope teams look at their contracts and ask: “Did we check type ranges? Did we use SafeCast? Did we assume the compiler would protect us?”
Because the compiler won’t. It does exactly what you tell it to, no more, no less. And if you tell it to cast a 18446744073709551615 into a negative, it will happily drain your reserve.
Art is the metadata of human emotion. So is code—except code’s emotion is trust. When you break that trust, you don’t get it back.
Curiosity is the new consensus mechanism. But vigilance is the new standard.