Rugproof Audit — examples/VulnerableVault.sol

2026-05-13 · commit HEAD · local

Summary

VulnerableVault is a 56-line ETH custody contract with a classic fund-draining reentrancy in withdraw() exploitable in a single tx, two privileged functions lacking access control, an unchecked external call, and an EOA admin with sweep authority. Not safe to deploy.
F
1
Critical
3
High
1
Medium
1
Low
0
Info

Trust report

Findings

REENT-001 Critical Reentrancy drains vault via CEI violation

Location

examples/VulnerableVault.sol:24

Summary

withdraw() sends ether before zeroing balance; attacker re-enters via receive() to drain the vault.

Code

function withdraw() external {
    uint256 amt = balance[msg.sender];
    (bool ok,) = msg.sender.call{value: amt}("");
    require(ok);
    balance[msg.sender] = 0;
}

Impact

Loss of 100% of user deposits in one tx. PoC demonstrates 1 ETH attacker → 11 ETH withdrawn.

Recommendation

Move state update before the external call (CEI). Or add nonReentrant.

Suggested patch

-    (bool ok,) = msg.sender.call{value: amt}("");
-    require(ok);
-    balance[msg.sender] = 0;
+    balance[msg.sender] = 0;
+    (bool ok,) = msg.sender.call{value: amt}("");
+    require(ok);
ACCESS-001 High setPaused has no access control

Location

examples/VulnerableVault.sol:34

Summary

Anyone can call setPaused(bool) — pause state controllable by adversary.

Code

function setPaused(bool p) external { paused = p; }

Impact

State pollution today; full DoS if any future function gains whenNotPaused gating.

Recommendation

Add onlyOwner modifier.

ACCESS-002 High rescue can drain user deposits

Location

examples/VulnerableVault.sol:40

Summary

Owner can transfer entire contract balance (= user deposits) anywhere with no allowlist or timelock.

Code

function rescue(address payable to) external onlyOwner { to.transfer(address(this).balance); }

Impact

Single-key compromise = 100% user fund loss.

Recommendation

Remove or scope to non-deposit assets; add Timelock + multi-sig.

UNCHK-001 High Low-level call return value ignored

Location

examples/VulnerableVault.sol:46

Summary

depositor.call(data) silently swallows failure.

Code

depositor.call(data);

Impact

Silent notification failures; gas-grief surface.

Recommendation

Capture return value; require(ok).

CENT-001 Medium Single-EOA owner with no two-step transfer

Location

examples/VulnerableVault.sol:50

Summary

transferOwnership applies immediately; typo can permanently brick contract.

Code

function transferOwnership(address newOwner) external onlyOwner { owner = newOwner; }

Impact

Operational risk on owner rotation.

Recommendation

Use Ownable2Step.

PRAGMA-001 Low Floating pragma ^0.8.20

Location

examples/VulnerableVault.sol:2

Summary

Pin compiler version to match audited bytecode.

Code

pragma solidity ^0.8.20;

Impact

Future minor compiler updates can change codegen.

Recommendation

pragma solidity 0.8.24;