masonzeng702550/secure-vault-solidity
GitHub: masonzeng702550/secure-vault-solidity
Stars: 0 | Forks: 0
# SecureVault
A secure Solidity ETH vault contract with multi-layered defense against common smart contract attacks. Built and tested with Foundry.
[](https://docs.soliditylang.org/)
[](https://book.getfoundry.sh/)
[](LICENSE)
[](test-results/forge-test-output.txt)
## Features
- **ETH deposits / withdrawals** with per-transaction withdrawal cap (`MAX_WITHDRAW = 10 ether`)
- **Internal balance transfers** between users (gas-efficient, no external call)
- **Two-tier access control** — `Owner` + `Operator` role
- **Pausable** — Operator can halt deposits/withdrawals in emergencies
- **Emergency drain** — Owner-only fallback to rescue funds
- **Custom errors** — gas-optimized revert reasons (Solidity 0.8.4+)
- **Reentrancy guard** — handcrafted 1/2 status pattern (no OZ dependency)
- **Safe ETH transfer** — uses `call{value:..}` with success check
- **Defensive fallback** — unknown function calls revert
## Security mechanisms
| Threat | Defense |
| ----------------------------------- | -------------------------------------------- |
| Reentrancy attack | `nonReentrant` modifier + CEI pattern |
| Integer overflow / underflow | Solidity 0.8+ built-in checks |
| Unauthorized access | `onlyOwner` / `onlyOperator` modifiers |
| ETH transfer failure (silent) | `call{value:..}` + boolean check |
| Emergency lockdown | `Pausable` (`whenNotPaused`) |
| Zero address / zero amount | `ZeroAddress` / `ZeroAmount` custom errors |
| Unknown function invocation | `fallback()` reverts |
## Project layout
secure-vault-solidity/
├── src/
│ └── SecureVault.sol # Main contract
├── test/
│ └── SecureVault.t.sol # Foundry test suite + ReentrancyAttacker
├── test-results/
│ ├── forge-test-output.txt # Captured `forge test -vv` output
│ └── forge-gas-report.txt # Captured `forge test --gas-report` output
├── foundry.toml
├── LICENSE
└── README.md
## Getting started
### Prerequisites
- [Foundry](https://book.getfoundry.sh/getting-started/installation) (forge, anvil, cast)
Install Foundry if you haven't:
curl -L https://foundry.paradigm.xyz | bash
foundryup
### Clone and install dependencies
git clone https://github.com/masonzeng702550/secure-vault-solidity.git
cd secure-vault-solidity
forge install foundry-rs/forge-std --no-commit
### Build
forge build
### Run tests
# Standard run with verbose output
forge test -vv
# With gas report
forge test --gas-report
# Run only the reentrancy attack test
forge test --match-test test_ReentrancyAttack_IsBlocked -vvvv
## Test suite
14 tests covering every public function and the major attack vectors:
| # | Test | Category | Status |
| -- | ------------------------------------------------- | ----------------- | ------ |
| 1 | `test_Deposit_UpdatesBalance` | Deposit | PASS |
| 2 | `test_Deposit_RevertsOnZero` | Input validation | PASS |
| 3 | `test_Receive_AccountsAsDeposit` | Receive fallback | PASS |
| 4 | `test_Withdraw_Success` | Withdraw | PASS |
| 5 | `test_Withdraw_RevertsOnExceedLimit` | Limit check | PASS |
| 6 | `test_Withdraw_RevertsOnInsufficientBalance` | Balance check | PASS |
| 7 | `test_TransferTo_MovesInternalBalance` | Internal transfer | PASS |
| 8 | `test_OnlyOwner_CanTransferOwnership` | Access control | PASS |
| 9 | `test_Operator_CanPause_NonOperator_Cannot` | Role permission | PASS |
| 10 | `test_WhenPaused_DepositReverts` | Pausable | PASS |
| 11 | **`test_ReentrancyAttack_IsBlocked`** | **Security** | **PASS** |
| 12 | `test_EmergencyWithdraw_OnlyOwner` | Owner-only | PASS |
| 13 | `test_EmergencyWithdraw_DrainsContract` | Emergency drain | PASS |
| 14 | `test_Fallback_Reverts` | Fallback | PASS |
### Reentrancy test highlight
The suite includes a real `ReentrancyAttacker` contract that:
1. Deposits ETH into the vault
2. Calls `withdraw()`, which triggers ETH transfer to the attacker
3. In `receive()`, immediately calls `withdraw()` again — attempting the classic recursive drain
Result: the second call is blocked by `nonReentrant`, the outer ETH transfer fails with `TransferFailed`, the attack reverts atomically, and the victim's deposit remains untouched.
[PASS] test_ReentrancyAttack_IsBlocked() (gas: 385,801)
See [`test-results/forge-test-output.txt`](test-results/forge-test-output.txt) for the full raw run.
## Gas report (excerpt)
| Function | Min | Avg | Max | Calls |
| ------------------- | ------ | ------ | ------ | ----- |
| `deposit` | 23,489 | 60,501 | 69,752 | 10 |
| `withdraw` | 29,288 | 35,362 | 45,287 | 3 |
| `transferTo` | 54,156 | 54,156 | 54,156 | 1 |
| `emergencyWithdraw` | 24,171 | 43,470 | 62,769 | 2 |
| `pause` | 25,733 | 27,817 | 29,974 | 3 |
| `setOperator` | 48,254 | 48,254 | 48,254 | 1 |
| `transferOwnership` | 24,170 | 24,170 | 24,170 | 1 |
| `fallback` | 21,552 | 21,552 | 21,552 | 1 |
Full report: [`test-results/forge-gas-report.txt`](test-results/forge-gas-report.txt)
## Design notes
### Why a hand-rolled `nonReentrant`?
Using a 1/2 status flag (instead of `bool`) saves gas — toggling between two non-zero values avoids the cold SSTORE cost on every call. This is the same pattern OpenZeppelin uses internally, replicated here to keep the contract dependency-free.
### Why custom errors instead of `require(.., "msg")`?
Custom errors (Solidity 0.8.4+) are cheaper at both deploy time and runtime, and they can carry typed parameters for easier debugging:
revert InsufficientBalance(requested, available);
### Why `call{value:..}` instead of `transfer`?
`transfer` and `send` forward only 2,300 gas, which breaks when the recipient is a contract with non-trivial fallback logic. `call` forwards all remaining gas — paired with the CEI pattern and `nonReentrant`, this is safe and forward-compatible.
### Checks-Effects-Interactions
Every state-changing function follows the strict CEI pattern: validate inputs, update state, then make external calls. Even if `nonReentrant` were removed, a reentrant call would see already-updated balances and fail naturally — defense in depth.
## Out of scope (room for improvement)
The contract intentionally focuses on a single, well-defended ETH vault. Topics not covered:
- Cross-function and read-only reentrancy
- Front-running / MEV mitigation (commit-reveal, TWAP)
- Signature replay protection (EIP-712 / EIP-1271)
- Upgradeable patterns (UUPS / Transparent Proxy) and storage layout
These would each warrant their own contracts and dedicated test files.
## License
[MIT](LICENSE)