Smart Contract Security Researcher |Breaking down real vulnerabilities | Shadow auditing protocols | Building attack intuition in public | bug bounties & audit
π±
Sometimes i feel like I should restart this Smart Contract Security Researcher carrier afresh, like go back to #cyfrinupdraft start with introduction to blockchain all over again till where I currently stand which is shadow auditing, because i speed run the whole course when I did, and now I have quite the gaps in foundation which is slowing things down some more. The struggle continues, still not giving up.
π±
Today was spent researching and studying Fuzz testing, both stateless and invariant-fuzz testing,
Iβll spend quite the time here, pulled up the specific TSWAP video from @CyfrinUpdraft smart contract security course, where @PatrickAlphaC taught this topic at greater length, after that used Claude Ai for more explanations, even questions that may be considered silly by those who already know it. Iβm still digging.
If you donβt get it the first time, come back after more exposure, repeat till it sinks.
Most importantly, practice, practice, practice.
@j_pvilla@Panoptic_xyz Frankly speaking, the money in blockchain is too juicy, however Iβve had more fun learning new things every week while climbing the ladder to get a share of the pie (money). Hope you having fun in your field?
π±
Another Day Learning Blockchain:π
Tried shadow Auditing one of past @Panoptic_xyz Protocol Audits from C4, been some days, it was initially difficult for me to navigate code base, map architecture and create mental model to work with,
However with several hours to days of trial and error, fail fail fail yet kept pushing at it, I find it much easier now to navigate code base, map architecture and create mental model to work it, though after few days on this Panoptic protocol, I found no bug, checked the Audit report, realized 2 highs where found in place i overlooked, it wasnβt in the external calls or entries points, but protocol math mechanism,
That lead me to think of fuzzing, stateless and invariant fuzz testing, so Iβll spend some time working on that.
#solidity #evm #audit #blockchain #web3 #c4
π±
Another Day Learning Blockchain:π
Last post's pattern works because an AMM's reserves are an honest, real-time price β which is exactly the problem. I built the simplest possible proof of that: a bare constant-product pool, one attacker, one victim, 0% fees, and I hand-verified every number before I let myself trust the Foundry console log.
I built a self-contained test setup for this β MockERC20 with no OpenZeppelin dependency, a SimpleAMM running the constant-product formula with 0% fees as an intentional simplification, plus separate Attacker, Victim, and Test contracts. The pricing formula: amountOut = reserveOut * amountIn / (reserveIn + amountIn).
Before trusting the test output, I walked the full 3-stage trade, this is doable as it's very small and simple compared to huge economic logic of big protocols.
Stage 1 β the attacker buys ETH with 150,000 USDC. Output: 42.857 ETH. Reserves shift to 57.143 ETH / 350,000 USDC.
Stage 2 β the victim buys ETH with 50,000 USDC, at the price the attacker just moved. Output: 7.143 ETH. Reserves settle at 50 ETH / 400,000 USDC.
Stage 3 β the attacker sells the 42.857 ETH back into the pool. Output: 184,615.38 USDC.
Net attacker profit: USDC balance went from 200,000 before the sequence to 234,615.38 after β a profit of 34,615.38 USDC, matching the console log exactly. The hand math and the test agreeing isn't a formality. It's the difference between "the contract logic is correct" and "the contract logic produced a number I didn't check."
For this to work, you need two things stacked together: a swap function whose signature takes only an input amount (and maybe a recipient) with no caller-supplied minimum-output parameter β no amountOutMin, no minOut, nothing equivalent β combined with a pricing function that reads live reserves directly (a reserveB * 1e18 / reserveA pattern) instead of a time-weighted or external feed.
The fix stacks the same way the vulnerability does. Enforce a caller-supplied amountOutMin, checked with a require before the transfer completes. Add a deadline parameter. For any protocol-level logic that consumes the AMM's price β not just the user-facing swap β use a TWAP or an external oracle instead of the pool's own spot price. And route user-facing swaps through MEV-protected RPCs to cut public mempool visibility in the first place.
This is the mechanism underneath a lot of what gets called "just MEV" β same reserves-as-price assumption as last post's oracle pattern, just extracted a block earlier and from a different victim.
#solidity #EVM #smartcontract #MEV #sandwich #audit
@jvst_tammy I use notion to create database for Attack patterns and more, that may help.
Good work finding something from that protocol, I found nothing in mine after 4 days, decided to close it and compare to original report, bugs found where in place I didnβt look at (2 highs), not the obvious external calls, but calculation mechanism of the protocol. Yet to post about it.
π±
Another Day Learning Blockchain:π
A sandwich attack doesn't need a clever exploit β it needs a pool that trusts its own spot price.
MEV research today was on paper, not in Foundry β I mapped a sandwich attack before writing a single line of Solidity.
MEV (Maximal Extractable Value) is any profit an attacker pulls by controlling transaction order inside a block. A sandwich attack is the cleanest version of it: the attacker sees a victim's swap sitting unconfirmed, buys the same asset first to push the price up (frontrun), lets the victim's trade fill against that inflated price with no way to stop it (execute), then sells back into the price they just created (backrun). The spread between the attacker's buy and sell is the take, and the victim pays for it in slippage they never agreed to.
None of that works unless the AMM prices trades directly off current reserves with nothing between the pool and the trade β no delay, no averaging, no resistance. That precondition is the whole attack surface: if a pool checked price against any external reference, or required a trade to clear over multiple blocks instead of one, the frontrun/backrun sequence couldn't manufacture a price to sell into.
Practical work today was a paper sketch, not code. The attack structure is five contracts: two ERC20s, the AMM, an Attacker contract, and a Victim contract β plus the test harness to wire them together. No Solidity written yet.
Take Away:
Spot-price-as-oracle is going into my Pattern Database as its own entry β any pool with no manipulation resistance is exploitable by whoever controls ordering around a trade, whether that's one flash-loaned transaction or two adjacent ones in the same block.
Next post is the practical side β the actual Foundry contracts and the result, look forward to it.
#solidity #EVM #smartcontract #sandwichAttack #exploit #web3
π±
Another Day Learning Blockchain:π
Every protocol is different.
The bugs usually aren't.
Here's one attack pattern I've added to my security notes after reproducing real-world exploits in Foundry.
Every exploit I reproduce in Foundry ends the same way: I write up the PoC, then I strip the specific protocol out and log the underlying pattern separately, so I can recognize it cold the next time it shows up wearing a different name. This is the first one of those entries I'm posting instead of keeping it private.
Pattern:->
Spot-Price Collateral Oracle Manipulation (Same-Block AMM Reserve Read), Vulnerability Class: Price Oracle Manipulation / Flash Loan Exploit
Entry Point Signal:->
A lending or vault contract's collateral-valuation path calls a price feed that itself derives value from an AMM pool's live reserves or LP exchange rate β balanceOf(), getReserves(), get_virtual_price(), pricePerShare() composed together β instead of a time-weighted or externally-anchored source. If you see a getUnderlyingPrice()-style function with no observe() or cumulative-price mechanism anywhere in the call chain, that's the signal.
Root Cause:->
The oracle prices an asset from a pool's token balances in the same transaction it's read, with zero time-averaging or manipulation resistance. Anyone with enough capital β real or flash-loaned β can skew those balances and have the skewed price accepted immediately by a downstream borrow check.
----------------------------------------------------------------
Attack Path
1: Flash loan a large amount of the manipulation asset.
2: Swap it into the AMM pool the target's oracle reads from, skewing reserves.
3: Deposit the resulting tokens as collateral in the target protocol.
4:Immediately borrow against that collateral, valued by the just-skewed oracle.
5:Unwind β swap back to repay the flash loan.
6: Keep the difference. The loan is repaid same-transaction, so no real capital was ever at risk.
---------------------------------------------------------------
Real World Example:->
Inverse Finance's Frontier market, June 16 2022. The attacker flash-loaned 27,000 WBTC from Aave V2, routed part of it through the Curve tricrypto pool, and pushed the crv3crypto LP price the protocol's oracle read from roughly 979 to 2,831. Against that inflated collateral value they borrowed ~10.13M DOLA, unwound the position, and walked away with ~53 WBTC and ~$100K USDT β about $1.2M total, extracted in a single transaction. Worth separating this cleanly from the much larger April 2022 Inverse Finance incident β that one wasn't a flash loan at all, it was a slower, capital-intensive TWAP manipulation spread across two blocks. Different mechanism, same underlying lesson: don't let a borrower influence the price you're about to lend against. (my last post)
Foundry Detection:->
Fork at or near the target block. In a PoC, query the oracle's reported price before and after a same-transaction swap through the pool it reads from β if a single transaction can move the reported price materially, there's no manipulation resistance. Cross-check against a live trace with cast run --etherscan-api-key, separating emit lines from STATICCALL lines to isolate exactly which read produced the price.
Standard Fix:->
Time weighted average price over a meaningful window, or an external oracle β Chainlink, for example β that isn't derived from the pool being used as collateral. Add per-block price-movement circuit breakers. Never price an LP or vault token from a live balanceOf() / get_virtual_price() composition without manipulation resistance somewhere in the chain.
If you've seen this pattern hiding behind a wrapper most people don't check first β a vault's pricePerShare() feeding a lending market instead of a raw AMM reserve read β reply with the protocol. I'm adding read-path variants to this entry as I find them.
#solidity #EVM #web3 #smartcontract #exploit