Findings. One critical unresolved.
withdraw()
Vault.sol:184
▶
The withdraw() function sends ETH to msg.sender before updating the balance mapping. A malicious contract can re-enter withdraw() in its receive() fallback, draining the vault before any balance is decremented. SWC-107.
183 require(balances[msg.sender] >= amount);
184 (bool ok,) = msg.sender.call{value: amount}(""); // ← sends before state update
185 require(ok);
186 balances[msg.sender] -= amount; // ← too late
187}
Apply the Checks-Effects-Interactions pattern: update balances[msg.sender] before the external call. Alternatively add a nonReentrant modifier from OpenZeppelin's ReentrancyGuard.
transferOwnership()
Access.sol:92
▶
transferOwnership() is callable by any address. An attacker can immediately become the contract owner and drain protocol funds or pause the system. SWC-105.
Add onlyOwner modifier. Use OpenZeppelin's Ownable2Step for two-step ownership transfer with confirmation to prevent accidental transfers.
Reward accumulation uses an unchecked block. With large enough token balances this silently wraps, zeroing accumulated rewards and permanently blocking user withdrawals.
Remove the unchecked block or add explicit overflow guards. Solidity ≥0.8 checks by default — only use unchecked for provably safe loop counters.
Collateral pricing uses a single Uniswap V2 spot price read. A flash loan sandwich attack can manipulate this within a single block, inflating collateral value and draining the lending pool.
Use a time-weighted average price (TWAP) over at least 30 minutes, or integrate a Chainlink price feed as a secondary source with circuit-breaker logic.
Return value of a low-level .call() is discarded. Silent failures allow the function to continue execution as if the call succeeded, potentially leaving state inconsistent.
Always check the boolean return: (bool ok,) = addr.call{...}(""); require(ok, "call failed");
A storage variable is read inside a loop on every iteration. Cache it in a local memory variable before the loop to reduce gas cost by ~2100 gas per iteration.
Public and external functions are missing NatSpec documentation (@notice, @param, @return). Not a vulnerability but reduces auditability and user trust.
Findings are representative. Real results appear after your first scan.