Good teams still have blind spots. When you've been staring at the same codebase for months, you stop seeing it the way an attacker would.
That's exactly where an external security review adds value — fresh eyes, different mental model, no assumptions about how the code 'should' work.
Glad the review was useful.
We just wrapped a smart contract audit for KoinArcade — huge thanks to @_Alex_Cipher for the review.
Even strong dev teams miss things: business logic edge cases, architecture risks, gas traps.
A security-minded second pair of eyes isn’t optional in Web3 — it’s how you ship with confidence.
Audit report is added to both footer and Links page, if you wanna check it out
koinarcade.com/KoinArcade_Al…
NO SECURITY REVIEW
Many Web3 gaming projects still launch without an audit. Sometimes it's because the project is small, sometimes it's because of budget constraints. Regardless of the reason, unaudited code handling real users and real assets is always a risk.
MESSY CODEBASES
Security isn't only about the logic itself. Clear naming, proper comments, and clean architecture make a huge difference. The easier code is to understand, the easier it is to review, maintain, and secure.
REINVENTING THE WHEEL
If a trusted, battle-tested library already exists, use it. Building complex functionality from scratch usually introduces unnecessary risk. You're spending extra development time while increasing the chances of security issues.
SKIPPING ADVANCED TESTING
Coverage numbers alone don't tell the full story. Fuzz tests and invariant tests are incredibly valuable and should be part of every serious testing suite. They often uncover edge cases and attack paths that manual testing completely misses.
LOW TEST COVERAGE
This is still one of the most common issues I come across. If your coverage is sitting below 40%, you're barely testing your code. In most cases, you should be targeting 80%+ coverage, and ideally 90% or more. The more code you test, the fewer surprises you'll have later.
These are the public findings.
I may have something more significant — It's speculative but maybe possible.
@ApeironNFT — DM me. Worth a conversation.
A few lower-severity notes:
[I-1] Fulfilled breed discount data is never cleaned up — stale entries accumulate across thousands of breeds, adding minor gas overhead over time.
[I-2] Interface files allow any 0.8.x compiler while implementations pin to exactly 0.8.12. Harmless today, potential headache tomorrow.
[L-2] When users breed, they send RON to cover the VRF oracle fee. The contract accepts anything above the minimum — but only forwards the exact minimum to the oracle.
The rest? Permanently locked. No native RON withdrawal path exists.
[L-1] The system wallet address (used to verify discount vouchers) has no zero-address guard.
One bad admin transaction sets it to address(0) → every discounted breed fails for every user until manually fixed.
One require prevents this entirely.
[M-2] There's an admin function to adjust breeding cooldown penalties.
It does absolutely nothing.
additionBornBaseInterval = additionBornBaseInterval;
Someone forgot the underscore. The variable has been assigning itself since deployment :D
Took a look at @ApeironNFT's breeding contracts on Ronin.
0xA84a4F42C9A06b9f03646993E54e52d3B380a00F
Found some things worth talking about 🧵
These are the public findings.
I have higher severity issues I won't be dropping here.
@fableborne — DM me. Let's talk before this becomes a problem.
[L-4] merge() doesn't enforce descending index order
The NatSpec says fromIndices must be descending — the contract never checks it.
Ascending input causes reads from a stale memory snapshot → wrong amounts merged into positions.
Scoped to STAKE_EDITOR_ROLE, but still a footgun.
[L-2] unstakeAllBatch() reverts on locked positions instead of skipping them
unstakeAll() from the base contract handles locks gracefully — skips and continues.
unstakeAllBatch() doesn't. One locked position in the mix and the entire tx reverts.
Users with mixed positions are stuck.
[L-1] Off-by-one in _restake bounds check
positionIndex > positions.length should be >=
When index equals length exactly, the check passes and Solidity silently returns zeroed data from a memory array. Call fails eventually — but with the wrong error, making debugging a pain.
Took a look at @fableborne's PowerStaker contract on Ronin.
Found a few issues worth flagging 🧵
0xe566BD31d4AB690b7455ee2B99ECf88Eb01a7366
I tried to find the team.
Deployer address: 0x41179085C5C259F9E4cDAa348b93806A76F7B47D
No socials. No web presence.
Zero TVL means no users at risk, so full disclosure is fine. But if you're the deployer and you're reading this — fix finding #1


