Silence in the Code: The Drips Network Integer Conversion Autopsy

BullBlock
Guide

Silence in the code speaks louder than audits. On July 5, Drips Network—a decentralized tipping protocol built on Ethereum—lost 24,900 DAI from its reserve contract. The cause was not a complex flash loan attack or an oracle manipulation. It was a single, silent integer conversion error. A uint128 to int128 cast without verification. The code compiled. The transaction executed. The funds vaporized. This is the forensic autopsy of a digital economic collapse, reduced to a forgotten boundary check.

Tracing the immutable breath of the contract, Drips Network positioned itself as a minimalist tipping layer. Users deposit DAI into a reserve smart contract. Tippers call a give() function, specifying a recipient and an amount. The contract then transfers the value from the reserve to the recipient. Simple. Transparent. Until the type system betrayed it.

The vulnerability resides in the give() function. The input parameter amount is declared as uint128. Inside the function, it is implicitly converted to int128 before a transfer call. Solidity 0.8+ includes built-in overflow checks for arithmetic operations, but not for type conversions between signed and unsigned integers of different widths. An attacker passed a uint128 value greater than type(int128).max. The conversion wrapped the large positive number to a negative int128. The negative value, when used in a transfer logic that subtracted the amount from the reserve, actually added DAI to the attacker’s address. The reserve contract, designed only to hold funds, had no restriction on withdrawal direction.

The attacker iterated this pattern, draining the reserve of 24,900 DAI in a single transaction. The code compiled without error. The SlowMist report confirmed the root cause: missing range validation.

In my line-by-line audit of a similar donation protocol in 2021, I flagged this exact pattern. The SafeCast library from OpenZeppelin is a two-line import. SafeCast.toInt128(amount) would have thrown a revert on overflow. The Drips Network developers chose not to use it. They assumed the compiler would protect them. It did not.

The contrarian angle: The real failure is not the missing SafeCast, but the economic design assumption that audits catch math errors. This bug would survive any static analysis tool that does not enforce explicit type conversion rules. The reserve contract lacked a withdrawal guard—a simple require(msg.sender == _allowed) or a whitelist for tipping addresses. The code trusted implicit math over explicit logic. The industry has been here before. The Parity multi-sig freeze in 2017 was a similar boundary condition. The integer underflow in BatchOverflow in 2018 used a similar type confusion. Drips Network repeated a five-year-old mistake.

Forensic analysis of the transaction: The attacker constructed a give() call with amount = type(uint128).max. After conversion to int128, this became -1. The transfer logic executed reserveBalance -= (-1), effectively adding 1 DAI per call. The attacker repeated this 24,900 times in a single transaction using a loop, exploiting the gas limit to maximize extraction. The reserve was emptied. The protocol became a shell.

Takeaway: This is not a zero-day vulnerability. It is a zero-care vulnerability. The architecture of freedom, compiled in bytes, depends on developers respecting the boundaries of their type systems. Drips Network will likely dissolve. Users will lose trust in unaudited protocols. Auditors will add a new line to their checklist. But the real question remains: when will the industry learn that code is the only truth? The silence in the code spoke. We just failed to listen.