We're glad to share that we took 1st place in the @sukukfi audit contest at @code4rena!
This marks our second contest win after our previous victory at Cantina.
We were the only team to find all 4/4 confirmed bugs, earning the Top Gatherer bonus.
Huge thanks to SukukFi and Code4rena for a great contest.
The $40,000 SukukFi Audit Competition results are officially in! 🏆
Huge congrats to everyone who submitted valid findings!
And a special shoutout to @Audittens for for earning a spot on the Top 10 leaderboard for the last 90 days
Shoutout to @sukukfi for their commitment to security, full list of winners in thread!👇
Answer
The bug is literally hidden under the Christmas tree (🎄 in the code) — in the Solidity version number. This contract is compiled with Solidity 0.8.31, which contains the bug "Lost storage array write on slot overflow" (see https://t.co/4lgqJt27j0).
The chosen STORAGE_LOCATION is very close to 2²⁵⁶, so the huge array uint256[256][2**240] balances crosses the storage boundary. That means there exists an account for which balances[account]'s slots wrap around 2²⁵⁶.
When such an account calls withdrawAll(), the loop correctly transfers all tokens, but delete $.balances[account] does nothing. As a result, balances are never zeroed, and the attacker can call withdrawAll() repeatedly to drain tokens.
Huge respect to the @solidity_lang team for the thorough review and for pushing the fix. Happy we could help make the ecosystem a bit safer.
We reported this issue back in November 2024, and it's now fixed in Solidity ≥0.8.32.
The bug lived in a very dark corner of storage semantics: deletion and partial assignment operations on fixed-length storage arrays that cross the 2²⁵⁶-slot boundary. Rare, but real — and present since 0.1.0.
Full write-up here: https://t.co/9xSGEIGUQx
Christmas Quest for Auditors
The contract below implements a multi-token vault:
• there are 2²⁴⁰ accounts and 256 ERC20 tokens;
• assume no malicious or weird tokens (e.g., reentrant, fee-on-transfer, etc.);
• any address can become the controller of an unoccupied account;
• a user can deposit tokens into an account they control;
• a user can withdraw all tokens from an account they control.
Find the vulnerability: how can an attacker steal tokens using withdrawAll?
Hint: try to look deeper — the bug is hidden right under the Christmas tree 👇
Here is the story of how we found this bug.
We were not trying to find a compiler bug — we were just experimenting with ways to create storage collisions without using assembly.
One option is a dynamic array whose elements are huge fixed-size arrays (uint[2**256-1][]). This allows storage collisions while still being accepted by the compiler.
A small detail: push() on such an array works, because Solidity does not try to clear the element's storage — storage is zero by default. Otherwise, this construction would be unusable.
Out of curiosity, we then tested what happens when calling pop() on such an array. Intuitively, removing an element should require clearing its storage, which would be infeasible for such a large element. However, in our tests, pop() still succeeded with reasonable gas.
To understand what was actually happening, we created another test: we wrote non-zero data into the element, then called pop(), and checked whether the data was cleared. It wasn't.
Looking at the compiler source code revealed the cause: the cleanup loop compares storage slot addresses, and when the affected storage range crosses the 2²⁵⁶ boundary, the comparison breaks due to wraparound — so the loop never executes and the writes are skipped.
Huge respect to the @solidity_lang team for the thorough review and for pushing the fix. Happy we could help make the ecosystem a bit safer.
We reported this issue back in November 2024, and it's now fixed in Solidity ≥0.8.32.
The bug lived in a very dark corner of storage semantics: deletion and partial assignment operations on fixed-length storage arrays that cross the 2²⁵⁶-slot boundary. Rare, but real — and present since 0.1.0.
Full write-up here: https://t.co/9xSGEIGUQx
Back to 0.8.32:
▫️Important bugfix: Lost storage array write on slot overflow
A bug affecting certain array operations near the end of storage was reported by @Audittens and is fixed in this release.
Honored to take 1st place in the Morpho Vault v2 audit contest!
Big thanks to @MorphoLabs and @cantinaxyz for a great competition.
With this win, we've completed our Cantina royal flush — having earned 1st, 2nd, 3rd, 4th, and 5th places across different contests.
Check out all our participations here: https://t.co/cYX9vcL1Ml.
Morpho Vault v2 rewrites how vaults allocate capital, and researchers just put it to the test.
The results from the $200,000 @MorphoLabs audit competition are in:
🥇 @Audittens: $23,123.41
🥈 Outliers & ZeroEx: $14,282.03
🥉 @patitonar & @0x_0x37: $8,091.38
Full leaderboard below.
Big thanks to @cantinaxyz and @StoryProtocol for hosting this audit contest.
We're glad to have placed 3rd among many talented researchers. Congrats to everyone who participated!
With this contest we've crossed $100k in total earnings on Cantina and received the $100k Badge — check it out on our profile: https://t.co/x2I38CvtTy.
By the way, all our work (contests, bounties and private audits) is now organized here: https://t.co/cYX9vcLzBT.
We just wrapped up @StoryProtocol's competition. Top researchers took home over $330k in earnings! 🪐
Your top 3 ranked researchers are:
🥇 @zachobront: $134,034.26
🥈 anonymousjoe, @dhankx7, @10xhash, @0xLeFy [Team blitz]: $113,763.07
🥉 @Audittens: $83,356.36
Thank you to everyone that participated! Full leaderboard below.
We are excited to share the results of our private audits for the first time!
Thanks to the @zksync team for the collaboration and the opportunity to contribute to its security!
@Audittens has completed an audit of @zksync's ZK Gateway!
ZK Gateway is a shared layer for the Elastic Network that will enable fast interoperability in future upgrades. Audittens did a great job reporting many important findings.
Read the full report: https://t.co/nIzjXDTMBp
Smart contracts are not just about DeFi and NFTs — they can celebrate too!
If only there were an EVM opcode for fireworks… Until then, here's how we welcome the new year on-chain in a truly decentralized way: https://t.co/Jvqiqkl9zP
Wrapping up 2024 with a summary of the bugs we reported through @immunefi this year!
1. Critical-severity bug in a ZK-rollup that asked not to be named
- Status: Confirmed
- Paid reward: $100,000
2. Critical-severity bug in @Starknet
- Initially rejected by Starknet, but after requesting mediation through Immunefi, the report was confirmed as a legitimate critical-severity issue
- Paid reward: $30,000
3. High-severity bug in @Starknet
- Status: Confirmed
- Paid reward: $30,000
A huge thanks to both projects for the opportunity to collaborate through their bug bounty programs, and to @immunefi for their impartial mediation process.
We look forward to helping make protocols even more secure in 2025!
Thanks to everyone who participated in our Solidity inheritance challenge and congratulations to all who gave the correct answer B.getter2!
While @flack00n provided a valid thesis in his explanation, the root cause of this behaviour goes deeper. We will explain it thoroughly.
The tricky part in this challenge is where execution flow will jump during execution on line 9: either to line 4 or 16. When a virtual function has several definitions in the inheritance chain, Solidity compiler uses the "C3 Linearization" algorithm to build a flattened inheritance hierarchy.
Such hierarchy gives a deterministic method resolution order (MRO) – the order of contracts (from "most derived" to "most base-like") in which a method is searched:
- foobar() – calls foobar in the first contract from MRO, in which it's defined;
- super.foobar() – calls foobar in the first contract from MRO after the current contract, in which it's defined;
X.foobar() – calls foobar in contract X.
In our challenge, MRO = [B, A], and therefore getter1() refers to the B.getter1() implementation since B is the first contract which defines a method getter1.
This highlights the complexities of inheritance that can lead to unexpected behaviour. It's a great reminder for all smart contract auditors to dive deeper into the nuances of Solidity.
Stay tuned for more challenges to test and sharpen your skills!
How strong is your understanding of inheritance?
During one of our previous engagements, we encountered an unusual bug related to inheritance in Solidity. We're eager to share the essence of this issue with you and challenge your knowledge.
Below is a simplified version of the code. Could you determine what the method B.getter2() returns?
After answering the poll in the thread, feel free to explain your reasoning in the comments. Correct explanations will be awarded with likes!
How strong is your understanding of inheritance?
During one of our previous engagements, we encountered an unusual bug related to inheritance in Solidity. We're eager to share the essence of this issue with you and challenge your knowledge.
Below is a simplified version of the code. Could you determine what the method B.getter2() returns?
After answering the poll in the thread, feel free to explain your reasoning in the comments. Correct explanations will be awarded with likes!
@0xT1MOH@cantinaxyz@blast@eulerfinance As of now, we have a total of 5 security researchers spread across different contests, private engagements, and bug bounty programs.
We're excited to share the results of our recent participation on @cantinaxyz, marking our first and second engagements on the platform.
A special thanks to @blast and @eulerfinance for the opportunities, and to @cantinaxyz for hosting the competitions.
Although we don't entirely agree with the judges' decisions in Euler's contest, both competitions have provided us with valuable insights and fresh ideas. Moving forward, we plan to apply these learnings to elevate the quality of our work and aim for even better results.
See you on the leaderboard soon!
Better late than never: our first participation in the audit contest. Big thanks to @zkSync and @code4rena for organizing and fair judging.
Can we perform better? We're ready to reveal the answer soon.
We're excited to announce a new company, Audittens!
United by passion for top-level math and programming competitions, and inspired by the values of the blockchain industry, we've teamed up to improve Web3 security.
Stay tuned and don't miss cutting-edge findings from our team!