masonzeng702550/secure-vault-solidity
GitHub: masonzeng702550/secure-vault-solidity
一个用 Solidity 编写、基于 Foundry 测试的安全 ETH 金库合约,通过多层防御机制抵御重入等常见智能合约攻击。
Stars: 0 | Forks: 0
# SecureVault
一个安全的 Solidity ETH 金库合约,具有多层防御机制以抵御常见的智能合约攻击。使用 Foundry 构建和测试。
[](https://docs.soliditylang.org/)
[](https://book.getfoundry.sh/)
[](LICENSE)
[](test-results/forge-test-output.txt)
## 功能
- **ETH 存款 / 取款** 带有单笔交易取款上限 (`MAX_WITHDRAW = 10 ether`)
- **用户间内部余额转账**(Gas 高效,无外部调用)
- **双层访问控制** — `Owner` + `Operator` 角色
- **可暂停** — Operator 可以在紧急情况下停止存款/取款
- **紧急抽水** — 仅限 Owner 使用的资金救援后备方案
- **自定义错误** — 经过 Gas 优化的回退原因 (Solidity 0.8.4+)
- **重入保护** — 手写的 1/2 状态模式(无 OZ 依赖)
- **安全的 ETH 转账** — 使用 `call{value:..}` 并进行成功检查
- **防御性 fallback** — 未知函数调用会回退
## 安全机制
| 威胁 | 防御手段 |
| ----------------------------------- | -------------------------------------------- |
| 重入攻击 | `nonReentrant` 修饰符 + CEI 模式 |
| 整数溢出 / 下溢 | Solidity 0.8+ 内置检查 |
| 未经授权的访问 | `onlyOwner` / `onlyOperator` 修饰符 |
| ETH 转账失败(静默) | `call{value:..}` + 布尔值检查 |
| 紧急锁定 | `Pausable` (`whenNotPaused`) |
| 零地址 / 零金额 | `ZeroAddress` / `ZeroAmount` 自定义错误 |
| 未知函数调用 | `fallback()` 回退 |
## 项目布局
```
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
```
## 快速开始
### 前置条件
- [Foundry](https://book.getfoundry.sh/getting-started/installation) (forge, anvil, cast)
如果尚未安装,请安装 Foundry:
```
curl -L https://foundry.paradigm.xyz | bash
foundryup
```
### 克隆并安装依赖
```
git clone https://github.com/masonzeng702550/secure-vault-solidity.git
cd secure-vault-solidity
forge install foundry-rs/forge-std --no-commit
```
### 构建
```
forge build
```
### 运行测试
```
# 带详细输出的标准运行
forge test -vv
# 带 gas 报告
forge test --gas-report
# 仅运行 reentrancy 攻击测试
forge test --match-test test_ReentrancyAttack_IsBlocked -vvvv
```
## 测试套件
14 个测试涵盖了所有公共函数和主要攻击向量:
| # | 测试名称 | 分类 | 状态 |
| -- | ------------------------------------------------- | ----------------- | ------ |
| 1 | `test_Deposit_UpdatesBalance` | 存款 | PASS |
| 2 | `test_Deposit_RevertsOnZero` | 输入验证 | PASS |
| 3 | `test_Receive_AccountsAsDeposit` | 接收 fallback | PASS |
| 4 | `test_Withdraw_Success` | 取款 | PASS |
| 5 | `test_Withdraw_RevertsOnExceedLimit` | 限额检查 | PASS |
| 6 | `test_Withdraw_RevertsOnInsufficientBalance` | 余额检查 | PASS |
| 7 | `test_TransferTo_MovesInternalBalance` | 内部转账 | PASS |
| 8 | `test_OnlyOwner_CanTransferOwnership` | 访问控制 | PASS |
| 9 | `test_Operator_CanPause_NonOperator_Cannot` | 角色权限 | PASS |
| 10 | `test_WhenPaused_DepositReverts` | 可暂停 | PASS |
| 11 | **`test_ReentrancyAttack_IsBlocked`** | **安全** | **PASS** |
| 12 | `test_EmergencyWithdraw_OnlyOwner` | 仅限 Owner | PASS |
| 13 | `test_EmergencyWithdraw_DrainsContract` | 紧急抽水 | PASS |
| 14 | `test_Fallback_Reverts` | Fallback | PASS |
### 重入测试亮点
该套件包含一个真实的 `ReentrancyAttacker` 合约,它会:
1. 将 ETH 存入金库
2. 调用 `withdraw()`,触发向攻击者转账 ETH
3. 在 `receive()` 中,立即再次调用 `withdraw()` —— 尝试经典的递归抽水
结果:第二次调用被 `nonReentrant` 阻止,外部 ETH 转账因 `TransferFailed` 失败,攻击被原子性地回退,受害者的存款完好无损。
```
[PASS] test_ReentrancyAttack_IsBlocked() (gas: 385,801)
```
有关完整的原始运行记录,请参见 [`test-results/forge-test-output.txt`](test-results/forge-test-output.txt)。
## Gas 报告(节选)
| 函数 | 最小 | 平均 | 最大 | 调用次数 |
| ------------------- | ------ | ------ | ------ | ----- |
| `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 |
完整报告:[`test-results/forge-gas-report.txt`](test-results/forge-gas-report.txt)
## 设计说明
### 为什么使用手写的 `nonReentrant`?
使用 1/2 状态标志(而不是 `bool`)可以节省 Gas —— 在两个非零值之间切换避免了每次调用时的冷 SSTORE 成本。这与 OpenZeppelin 内部使用的模式相同,在此处复制以保持合约无依赖。
### 为什么使用自定义错误而不是 `require(.., "msg")`?
自定义错误 (Solidity 0.8.4+) 在部署时和运行时都更便宜,并且它们可以携带类型化参数以便于调试:
```
revert InsufficientBalance(requested, available);
```
### 为什么使用 `call{value:..}` 而不是 `transfer`?
`transfer` 和 `send` 仅转发 2,300 Gas,当接收方是具有复杂 fallback 逻辑的合约时,这会失效。`call` 转发所有剩余的 Gas —— 配合 CEI 模式和 `nonReentrant`,这是安全且向前兼容的。
### 检查-生效-交互 (Checks-Effects-Interactions)
每个更改状态的函数都遵循严格的 CEI 模式:验证输入,更新状态,然后进行外部调用。即使移除了 `nonReentrant`,重入调用也会看到已更新的余额并自然失败 —— 深度防御。
## 范围之外(改进空间)
该合约有意专注于单一的、防御严密的 ETH 金库。未涵盖的主题:
- 跨函数和只读重入
- Front-running / MEV 缓解(commit-reveal, TWAP)
- 签名重放保护 (EIP-712 / EIP-1271)
- 可升级模式(UUPS / Transparent Proxy)和存储布局
这些都需要各自独立的合约和专用的测试文件。
## 许可证
[MIT](LICENSE)
标签:Solidity, Web3安全, 以太坊, 区块链, 智能合约