Welcome to The Notorious Bug Digest #3—a curated compilation of insights into recent Web3 bugs and security incidents. When our security researchers aren’t diving into audits, they dedicate time to staying up-to-date with the latest happenings in the security space, analyzing audit reports, and dissecting on-chain incidents. We believe that this knowledge is invaluable to the broader security community, offering a resource for researchers to sharpen their skills and help newcomers navigate the world of Web3 security. Join us as we explore this batch of bugs together!
Scaling is often used to manage precision during division or unit conversion between different tokens. However, inconsistent scaling can lead to critical bugs, causing incorrect calculations, and significant financial losses. Below, we analyze a recent on-chain incident and an audit finding to illustrate this bug.
MBU Token - Mismatched Scaling
On May 11, 2025, the MBU token contract suffered a hack, resulting in a $2M loss. As shown in Image A, the attacker deposited 0.001 BNB into the contract, which then fetched the BNB/USDT and MBU/USDT prices from an oracle. Despite receiving accurate prices, the contract minted an enormous amount of MBU tokens, far exceeding the value of the deposited BNB, allowing the attacker to profit.
Image A
Examining the deposit
function (sign-up required) reveals the issue (Image B).
The deposit
function aims to calculate the MBU token amount that is equivalent to the deposited asset's value. It first calls the 0x371b
function (Image C) to compute the USDT value of the deposited asset.
Image C
If the deposited token is USDT, the function returns the amount directly without scaling. If it's BNB, it returns the USDT value, scaled by 1e18. This value is then passed to the 0x3039
function (Image D), which calculates the MBU token amount by dividing the input value by the MBU/USDT price, which is also scaled by 1e18. However, the 0x3039
function erroneously applies an additional 1e18 scaling to the input value before division. As a result, when BNB is deposited, the minted MBU token amount is inflated by a factor of 1e18, while USDT deposits yield correct amounts.
Image D
Across Protocol - Wrong Decimal Scaling in Gas Token Amount Calculation
This bug was identified during one of our audits of a cross-chain protocol. The contract handles custom gas tokens for a destination chain but fails to account for varying token decimal scales. It calculates the gas token amount required for operations using a formula that assumes an 18-decimal scale. However, tokens like USDC, which use 6 decimals, are not adjusted for their actual decimal scale. Consequently, the contract withdraws funds from a funder contract using the 18-decimal scaled amount, leading to significant overcharging. For USDC, this results in a withdrawal that is inflated by a factor of 10^(18-6)
= 10^12
, causing substantial financial losses.
Lessons Learnt
Exercise caution with multi-token operations and scaling. Comprehensive testing, including unit tests, can quickly identify these inconsistencies.
The OpenZeppelin team has been developing the Stellar Contracts Library, a set of smart contracts designed to serve as the building blocks to develop applications for the Stellar blockchain. An internal audit uncovered a medium severity issue which could revert calls that extended approval by more than the maximum TTL extension period allowed by Stellar. Stellar can store contract data for a limited amount of time, called Time to Live (TTL). The TTL can be periodically extended, and if it is not, the data will be archived or permanently deleted upon TTL expiration.
The fungible token implementation features an approval mechanism, similar to the ERC-20 standard. Approvals are stored in temporary storage (i.e., they expire after some time and are permanently deleted). When granting/updating an approval, users are able to specify an amount of time for the approval to be active: this updates the storage entry's TTL accordingly. However, Stellar has a Maximum TTL value by which a temporary storage entry can be extended. Attempting to increase an entry's TTL by more than the maximum value would cause the code to panic, and, hence, any long-term approvals (longer than the maximum TTL which was 1 year) would revert.
ZKsync introduced the SSO Account, a customizable smart account that can validate transactions through arbitrary mechanisms. Execution hooks can be attached to an account and will run before and after each transaction.
As shown in Image F, the preExecutionHook
function is run for each hook, followed by the transaction execution and the postExecutionHook
function for each hook.
However, the list of hooks is not cached, which can cause unexpected behavior if the list is altered between running the hooks. If transaction execution adds or removes any hook, the ordering within the list can become different, causing postExecutionHook
to be called in a different order. In addition, if a hook is removed, the transaction will revert due to accessing out-of-bounds storage.
The fix that was introduced cached the list of hooks while ensuring that the postExecutionHook
calls are run in the same order, and only if the hook has not been removed.
It’s important to emphasize that the intent behind this content is not to criticize or blame the affected projects but to provide objective overviews that serve as educational material for the community to learn from and better protect projects in the future.