Findings(5)
Reentrancy
The withdraw function allows for reentrancy attacks because it makes an external call to msg.sender before updating the state variable balances.
Use the Checks-Effects-Interactions pattern by updating the state variable balances before making the external call.
Unprotected Function
The deposit function is not protected against potential front-running attacks, allowing an attacker to manipulate the contract's state.
Add access control (or a commit-reveal / per-block guard) to deposit() so its share accounting cannot be front-run, and never derive shares from a value an attacker can move within the same transaction.
Unsecured Use of Transfer
The use of the transfer function (via msg.sender.call{value: bal}()) in the withdraw function can lead to issues if the recipient contract does not handle the transfer correctly.
Follow checks-effects-interactions: update the caller's balance before the external call, wrap withdraw() in OpenZeppelin's nonReentrant guard, and prefer a pull-payment pattern over forwarding all gas via call().
reentrancy-eth
Reentrancy in SampleVault.withdraw() (contracts/audit-targets/SampleVault.sol#14-20): External calls: - (ok,None) = msg.sender.call{value: bal}() (contracts/audit-targets/SampleVault.sol#17) State variables written after the call(s): - balances[msg.sender] = 0 (contracts/audit-targets/SampleVault.sol#19) SampleVault.balances (contracts/audit-targets/SampleVault.sol#7) can be used in cross function reentrancies: - SampleVault.balances (contracts/audit-targets/SampleVault.sol#7) - SampleVault.deposit() (contracts/audit-targets/SampleVault.sol#9-11) - SampleVault.withdraw() (contracts/audit-targets/SampleVault.sol#14-20)
Zero the caller's balance before sending ETH (checks-effects-interactions) and add OpenZeppelin's ReentrancyGuard (nonReentrant) to withdraw() so the external call cannot re-enter.
low-level-calls
Low level call in SampleVault.withdraw() (contracts/audit-targets/SampleVault.sol#14-20): - (ok,None) = msg.sender.call{value: bal}() (contracts/audit-targets/SampleVault.sol#17)
Check the boolean returned by the low-level call and revert on failure (require(ok, "transfer failed")); consider Address.sendValue or a pull-payment pattern instead of a raw call().