Solidity: Mini Lending - 完整测试套件 + Gas优化
### 一、DeFi 测试方法论
日期: 2026-06-29 方向: Solidity 阶段: 第四阶段:综合实战 标签: #MiniLending #测试 #Foundry #Gas优化 #DeFi #forge-test
今日目标
Mini Lending 协议的核心合约已经完成(存款/借贷/还款/清算),今天聚焦两个关键环节:
- 完整测试套件: 覆盖所有业务路径、边界条件和攻击场景的 20+ 测试用例
- Gas 优化: 在测试验证的保障下,对合约进行有针对性的 Gas 优化
DeFi 协议的测试不同于普通 DApp——你需要模拟多个参与者、时间推移、价格变动、极端市场条件。一个不充分的测试套件等于在主网上"裸奔"。
核心概念
一、DeFi 测试方法论
1.1 测试金字塔(DeFi 版)
┌──────────────┐
│ E2E / Fork │ ← 主网 Fork 测试(最真实)
─┤ Tests ├─
/ └──────────────┘ \
/ \
┌──────────────────────┐
│ Integration Tests │ ← 多合约交互、预言机模拟
─┤ (多合约协作) ├─
/ └──────────────────────┘ \
/ \
┌──────────────────────────────┐
│ Unit Tests (单函数) │ ← 每个函数的正确性
│ Happy Path + Edge Cases │
└──────────────────────────────┘
1.2 DeFi 特有的测试维度
| 测试维度 | 说明 | Mini Lending 示例 |
|---|---|---|
| Happy Path | 正常业务流程 | 存款→借款→还款 |
| 边界条件 | 极端参数 | 存0、借最大额、刚好触发清算 |
| 权限控制 | 未授权操作 | 非 owner 调用管理函数 |
| 重入攻击 | 回调攻击 | withdraw 中的重入 |
| 价格操纵 | 预言机攻击 | 价格骤降触发级联清算 |
| 时间依赖 | 利息累积 | 1年后的利息计算精度 |
| 多用户交互 | 博弈场景 | 多人同时清算同一仓位 |
| 数学精度 | 溢出/下溢 | 极大数/极小数的利息计算 |
二、测试基础设施搭建
// test/MiniLending.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/MiniLending.sol";
import "../src/mocks/MockERC20.sol";
import "../src/mocks/MockPriceOracle.sol";
contract MiniLendingTest is Test {
MiniLending public lending;
MockERC20 public collateralToken; // WETH
MockERC20 public borrowToken; // USDC
MockPriceOracle public oracle;
address public alice = makeAddr("alice");
address public bob = makeAddr("bob");
address public carol = makeAddr("carol"); // 清算者
address public owner = makeAddr("owner");
uint256 constant INITIAL_BALANCE = 100_000e18;
uint256 constant COLLATERAL_FACTOR = 7500; // 75%
uint256 constant LIQUIDATION_BONUS = 500; // 5%
uint256 constant ETH_PRICE = 2000e8; // $2000
function setUp() public {
vm.startPrank(owner);
// 部署 Mock 合约
collateralToken = new MockERC20("Wrapped ETH", "WETH", 18);
borrowToken = new MockERC20("USD Coin", "USDC", 6);
oracle = new MockPriceOracle();
// 设置价格
oracle.setPrice(address(collateralToken), ETH_PRICE);
oracle.setPrice(address(borrowToken), 1e8); // $1
// 部署 Lending 合约
lending = new MiniLending(address(oracle));
lending.addMarket(
address(collateralToken),
COLLATERAL_FACTOR,
LIQUIDATION_BONUS
);
lending.addMarket(
address(borrowToken),
8000, // 80% collateral factor
500 // 5% liquidation bonus
);
vm.stopPrank();
// 分发代币
collateralToken.mint(alice, INITIAL_BALANCE);
collateralToken.mint(bob, INITIAL_BALANCE);
borrowToken.mint(alice, INITIAL_BALANCE);
borrowToken.mint(bob, INITIAL_BALANCE);
borrowToken.mint(carol, INITIAL_BALANCE);
// 为 lending 合约提供流动性
borrowToken.mint(address(lending), 1_000_000e6);
// 用户 approve
vm.prank(alice);
collateralToken.approve(address(lending), type(uint256).max);
vm.prank(alice);
borrowToken.approve(address(lending), type(uint256).max);
vm.prank(bob);
collateralToken.approve(address(lending), type(uint256).max);
vm.prank(bob);
borrowToken.approve(address(lending), type(uint256).max);
}
// ==================== DEPOSIT 测试 ====================
/// @notice 测试 #1: 正常存款
function test_deposit_happyPath() public {
uint256 depositAmount = 10e18; // 10 WETH
vm.prank(alice);
lending.deposit(address(collateralToken), depositAmount);
assertEq(
lending.getDepositBalance(alice, address(collateralToken)),
depositAmount
);
assertEq(
collateralToken.balanceOf(address(lending)),
depositAmount
);
}
/// @notice 测试 #2: 存款金额为0应该 revert
function test_deposit_zeroAmount_reverts() public {
vm.prank(alice);
vm.expectRevert("Amount must be > 0");
lending.deposit(address(collateralToken), 0);
}
/// @notice 测试 #3: 多次存款累积
function test_deposit_multipleDeposits_accumulate() public {
vm.startPrank(alice);
lending.deposit(address(collateralToken), 5e18);
lending.deposit(address(collateralToken), 3e18);
vm.stopPrank();
assertEq(
lending.getDepositBalance(alice, address(collateralToken)),
8e18
);
}
/// @notice 测试 #4: 未支持的资产存款
function test_deposit_unsupportedAsset_reverts() public {
MockERC20 randomToken = new MockERC20("Random", "RND", 18);
randomToken.mint(alice, 100e18);
vm.startPrank(alice);
randomToken.approve(address(lending), type(uint256).max);
vm.expectRevert("Market not supported");
lending.deposit(address(randomToken), 10e18);
vm.stopPrank();
}
// ==================== WITHDRAW 测试 ====================
/// @notice 测试 #5: 正常取款
function test_withdraw_happyPath() public {
vm.startPrank(alice);
lending.deposit(address(collateralToken), 10e18);
lending.withdraw(address(collateralToken), 5e18);
vm.stopPrank();
assertEq(
lending.getDepositBalance(alice, address(collateralToken)),
5e18
);
}
/// @notice 测试 #6: 取款超过余额
function test_withdraw_exceedsBalance_reverts() public {
vm.startPrank(alice);
lending.deposit(address(collateralToken), 10e18);
vm.expectRevert("Insufficient balance");
lending.withdraw(address(collateralToken), 11e18);
vm.stopPrank();
}
/// @notice 测试 #7: 有未还借款时取款导致健康因子不足
function test_withdraw_wouldBreachHealthFactor_reverts() public {
vm.startPrank(alice);
lending.deposit(address(collateralToken), 10e18);
// 借出75%的额度 (最大)
lending.borrow(address(borrowToken), 15000e6); // 10 ETH * $2000 * 75% = $15000
// 尝试取走抵押品
vm.expectRevert("Health factor too low");
lending.withdraw(address(collateralToken), 1e18);
vm.stopPrank();
}
// ==================== BORROW 测试 ====================
/// @notice 测试 #8: 正常借款
function test_borrow_happyPath() public {
vm.startPrank(alice);
lending.deposit(address(collateralToken), 10e18);
// 10 ETH * $2000 * 75% = $15000 最大借款
lending.borrow(address(borrowToken), 10000e6); // 借$10000
vm.stopPrank();
assertEq(
lending.getBorrowBalance(alice, address(borrowToken)),
10000e6
);
}
/// @notice 测试 #9: 超额借款
function test_borrow_exceedsCollateral_reverts() public {
vm.startPrank(alice);
lending.deposit(address(collateralToken), 10e18);
vm.expectRevert("Insufficient collateral");
lending.borrow(address(borrowToken), 16000e6); // 超过$15000限额
vm.stopPrank();
}
/// @notice 测试 #10: 无抵押品借款
function test_borrow_noCollateral_reverts() public {
vm.prank(alice);
vm.expectRevert("Insufficient collateral");
lending.borrow(address(borrowToken), 1000e6);
}
/// @notice 测试 #11: 刚好借到最大额度
function test_borrow_exactMaxAmount() public {
vm.startPrank(alice);
lending.deposit(address(collateralToken), 10e18);
// 精确借到75%限额
lending.borrow(address(borrowToken), 15000e6);
vm.stopPrank();
// 健康因子应该刚好等于1
uint256 healthFactor = lending.getHealthFactor(alice);
assertApproxEqAbs(healthFactor, 1e18, 1e15); // 允许0.1%误差
}
// ==================== REPAY 测试 ====================
/// @notice 测试 #12: 全额还款
function test_repay_fullRepayment() public {
vm.startPrank(alice);
lending.deposit(address(collateralToken), 10e18);
lending.borrow(address(borrowToken), 5000e6);
// 时间推移累积利息
vm.warp(block.timestamp + 365 days);
uint256 totalOwed = lending.getBorrowBalance(alice, address(borrowToken));
borrowToken.approve(address(lending), totalOwed);
lending.repay(address(borrowToken), totalOwed);
vm.stopPrank();
assertEq(lending.getBorrowBalance(alice, address(borrowToken)), 0);
}
/// @notice 测试 #13: 部分还款
function test_repay_partialRepayment() public {
vm.startPrank(alice);
lending.deposit(address(collateralToken), 10e18);
lending.borrow(address(borrowToken), 10000e6);
lending.repay(address(borrowToken), 3000e6);
vm.stopPrank();
assertEq(
lending.getBorrowBalance(alice, address(borrowToken)),
7000e6
);
}
/// @notice 测试 #14: 还款金额为0
function test_repay_zeroAmount_reverts() public {
vm.prank(alice);
vm.expectRevert("Amount must be > 0");
lending.repay(address(borrowToken), 0);
}
// ==================== LIQUIDATION 测试 ====================
/// @notice 测试 #15: 正常清算流程
function test_liquidate_happyPath() public {
// Alice 存入 10 ETH,借出 $14000 USDC (接近最大额度)
vm.startPrank(alice);
lending.deposit(address(collateralToken), 10e18);
lending.borrow(address(borrowToken), 14000e6);
vm.stopPrank();
// ETH 价格下跌 30%: $2000 → $1400
oracle.setPrice(address(collateralToken), 1400e8);
// Carol 清算 Alice
uint256 liquidateAmount = 7000e6; // 清算一半
vm.startPrank(carol);
borrowToken.approve(address(lending), liquidateAmount);
lending.liquidate(alice, address(borrowToken), liquidateAmount);
vm.stopPrank();
// Carol 应该获得了折扣的抵押品
uint256 carolCollateral = collateralToken.balanceOf(carol);
assertTrue(carolCollateral > 0, "Liquidator should receive collateral");
// Alice 的借款减少
assertTrue(
lending.getBorrowBalance(alice, address(borrowToken)) < 14000e6,
"Borrower debt should decrease"
);
}
/// @notice 测试 #16: 健康仓位不能被清算
function test_liquidate_healthyPosition_reverts() public {
vm.startPrank(alice);
lending.deposit(address(collateralToken), 10e18);
lending.borrow(address(borrowToken), 5000e6); // 保守借款
vm.stopPrank();
vm.startPrank(carol);
borrowToken.approve(address(lending), 5000e6);
vm.expectRevert("Position is healthy");
lending.liquidate(alice, address(borrowToken), 5000e6);
vm.stopPrank();
}
/// @notice 测试 #17: 清算奖励计算正确
function test_liquidate_bonusCalculation() public {
vm.startPrank(alice);
lending.deposit(address(collateralToken), 10e18);
lending.borrow(address(borrowToken), 14000e6);
vm.stopPrank();
// 价格下跌
oracle.setPrice(address(collateralToken), 1400e8);
uint256 liquidateAmount = 1400e6; // 清算 $1400
uint256 carolCollateralBefore = collateralToken.balanceOf(carol);
vm.startPrank(carol);
borrowToken.approve(address(lending), liquidateAmount);
lending.liquidate(alice, address(borrowToken), liquidateAmount);
vm.stopPrank();
uint256 carolCollateralAfter = collateralToken.balanceOf(carol);
uint256 received = carolCollateralAfter - carolCollateralBefore;
// 预期: $1400 / $1400(新ETH价格) * 1.05(清算奖励) = 1.05 ETH
uint256 expectedCollateral = 1.05e18;
assertApproxEqRel(received, expectedCollateral, 0.01e18); // 1%误差
}
// ==================== INTEREST 测试 ====================
/// @notice 测试 #18: 利息累积正确
function test_interest_accruesOverTime() public {
vm.startPrank(alice);
lending.deposit(address(collateralToken), 10e18);
lending.borrow(address(borrowToken), 10000e6);
vm.stopPrank();
uint256 borrowBefore = lending.getBorrowBalance(alice, address(borrowToken));
// 推进1年
vm.warp(block.timestamp + 365 days);
uint256 borrowAfter = lending.getBorrowBalance(alice, address(borrowToken));
// 借款应该增加(利息)
assertTrue(borrowAfter > borrowBefore, "Interest should accrue");
// 假设年利率5%,检查大致范围
uint256 expectedInterest = (borrowBefore * 500) / 10000; // 5%
uint256 actualInterest = borrowAfter - borrowBefore;
assertApproxEqRel(actualInterest, expectedInterest, 0.05e18); // 5%误差
}
// ==================== ATTACK 场景测试 ====================
/// @notice 测试 #19: 重入攻击防护
function test_attack_reentrancy_reverts() public {
// 部署恶意合约尝试重入
ReentrancyAttacker attacker = new ReentrancyAttacker(address(lending));
collateralToken.mint(address(attacker), 100e18);
vm.prank(address(attacker));
collateralToken.approve(address(lending), type(uint256).max);
// 攻击应该失败
vm.expectRevert(); // ReentrancyGuard 或其他保护
attacker.attack(address(collateralToken), 10e18);
}
/// @notice 测试 #20: 价格操纵 — 闪电贷场景模拟
function test_attack_priceManipulation() public {
vm.startPrank(alice);
lending.deposit(address(collateralToken), 10e18);
lending.borrow(address(borrowToken), 10000e6);
vm.stopPrank();
// 模拟: 攻击者在同一区块内操纵价格
// 好的预言机设计应该使用 TWAP 而非即时价格
oracle.setPrice(address(collateralToken), 100000e8); // 价格暴涨
// 即使价格暴涨,已有借款不应该允许借更多(除非有刷新机制)
// 这里测试的是预言机更新后的行为是否合理
vm.startPrank(alice);
// 价格涨了应该可以借更多 — 但需要有TWAP保护
lending.borrow(address(borrowToken), 100000e6);
vm.stopPrank();
// 价格恢复正常
oracle.setPrice(address(collateralToken), ETH_PRICE);
// Alice 现在应该是可清算状态
uint256 healthFactor = lending.getHealthFactor(alice);
assertTrue(healthFactor < 1e18, "Should be liquidatable after price reverts");
}
/// @notice 测试 #21: 自我清算
function test_selfLiquidation_reverts() public {
vm.startPrank(alice);
lending.deposit(address(collateralToken), 10e18);
lending.borrow(address(borrowToken), 14000e6);
vm.stopPrank();
oracle.setPrice(address(collateralToken), 1400e8);
// Alice 尝试自我清算
vm.startPrank(alice);
borrowToken.approve(address(lending), 7000e6);
vm.expectRevert("Cannot self-liquidate");
lending.liquidate(alice, address(borrowToken), 7000e6);
vm.stopPrank();
}
// ==================== FUZZ 测试 ====================
/// @notice 测试 #22: Fuzz 存款金额
function testFuzz_deposit(uint256 amount) public {
amount = bound(amount, 1, INITIAL_BALANCE); // 限制范围
vm.prank(alice);
lending.deposit(address(collateralToken), amount);
assertEq(
lending.getDepositBalance(alice, address(collateralToken)),
amount
);
}
/// @notice 测试 #23: Fuzz 借款不超过抵押限额
function testFuzz_borrow_withinLimit(uint256 borrowAmount) public {
uint256 depositAmount = 10e18;
uint256 maxBorrow = (depositAmount * uint256(ETH_PRICE) * COLLATERAL_FACTOR)
/ (1e8 * 10000 * 1e12); // 调整 decimals
borrowAmount = bound(borrowAmount, 1, maxBorrow);
vm.startPrank(alice);
lending.deposit(address(collateralToken), depositAmount);
lending.borrow(address(borrowToken), borrowAmount);
vm.stopPrank();
assertEq(
lending.getBorrowBalance(alice, address(borrowToken)),
borrowAmount
);
}
// ==================== GAS SNAPSHOT 测试 ====================
/// @notice 测试 #24: Gas 基准测试
function test_gas_deposit() public {
vm.prank(alice);
uint256 gasBefore = gasleft();
lending.deposit(address(collateralToken), 10e18);
uint256 gasUsed = gasBefore - gasleft();
emit log_named_uint("Gas used for deposit", gasUsed);
assertTrue(gasUsed < 100_000, "Deposit should cost less than 100k gas");
}
function test_gas_borrow() public {
vm.startPrank(alice);
lending.deposit(address(collateralToken), 10e18);
uint256 gasBefore = gasleft();
lending.borrow(address(borrowToken), 5000e6);
uint256 gasUsed = gasBefore - gasleft();
vm.stopPrank();
emit log_named_uint("Gas used for borrow", gasUsed);
assertTrue(gasUsed < 150_000, "Borrow should cost less than 150k gas");
}
}
// ==================== 辅助合约 ====================
contract ReentrancyAttacker {
MiniLending public lending;
bool public attacking;
constructor(address _lending) {
lending = MiniLending(_lending);
}
function attack(address token, uint256 amount) external {
attacking = true;
lending.deposit(token, amount);
lending.withdraw(token, amount);
}
// ERC20 转账回调中尝试重入
fallback() external {
if (attacking) {
attacking = false;
// 尝试再次取款
// lending.withdraw(...);
}
}
}
三、Gas 优化策略应用
3.1 存储优化: Slot Packing
// 优化前: 3个 storage slot (每个 slot 32 bytes)
struct UserPositionBefore {
uint256 depositAmount; // slot 0: 32 bytes
uint256 borrowAmount; // slot 1: 32 bytes
uint256 lastUpdateTime; // slot 2: 32 bytes
}
// 读取成本: 3 * 2100 = 6300 gas (冷读取)
// 优化后: 2个 storage slot
struct UserPositionAfter {
uint128 depositAmount; // slot 0: 16 bytes ┐
uint128 borrowAmount; // slot 0: 16 bytes ┘ (同一个 slot)
uint64 lastUpdateTime; // slot 1: 8 bytes
uint64 interestIndex; // slot 1: 8 bytes
uint128 reserved; // slot 1: 16 bytes (预留)
}
// 读取成本: 2 * 2100 = 4200 gas (冷读取), 节省 33%
3.2 用 immutable 替代 storage 读取
// 优化前: 每次调用都读 storage
contract LendingBefore {
address public oracle;
uint256 public liquidationBonus;
uint256 public collateralFactor;
function getHealthFactor(address user) public view returns (uint256) {
// 3次 SLOAD: 3 * 2100 = 6300 gas
uint256 price = IPriceOracle(oracle).getPrice(token);
uint256 maxBorrow = collateral * collateralFactor / 10000;
// ...
}
}
// 优化后: 使用 immutable
contract LendingAfter {
address public immutable oracle; // 编译进 bytecode
uint256 public immutable liquidationBonus; // 读取只需 3 gas
uint256 public immutable collateralFactor; // vs SLOAD 的 2100 gas
constructor(address _oracle, uint256 _bonus, uint256 _factor) {
oracle = _oracle;
liquidationBonus = _bonus;
collateralFactor = _factor;
}
}
3.3 使用 unchecked 优化安全的算术
function _calculateInterest(
uint256 principal,
uint256 ratePerSecond,
uint256 timeElapsed
) internal pure returns (uint256) {
// 安全的场景下使用 unchecked 节省 gas
// 前提: 我们已经验证不会溢出
unchecked {
// 这些值在合理范围内,不会溢出 uint256
uint256 interest = (principal * ratePerSecond * timeElapsed) / 1e18;
return interest;
}
}
// 循环中的优化
function _updateMultiplePositions(address[] calldata users) external {
uint256 len = users.length;
for (uint256 i; i < len;) {
_updatePosition(users[i]);
unchecked { ++i; } // 节省约 60 gas/iteration
}
}
3.4 使用 calldata 替代 memory
// 优化前: memory 会复制数据
function batchLiquidate(address[] memory users) external {
// 数组被复制到内存,浪费 gas
}
// 优化后: calldata 直接读取 calldata
function batchLiquidate(address[] calldata users) external {
// 直接从 calldata 读取,不复制
// 节省: 每个元素 ~60 gas
}
3.5 自定义错误替代字符串
// 优化前: 字符串消耗更多 gas (部署+运行时)
require(amount > 0, "Amount must be greater than zero");
// 错误字符串存储在 bytecode 中
// 优化后: 自定义错误
error AmountZero();
error InsufficientCollateral(uint256 required, uint256 available);
error PositionHealthy(uint256 healthFactor);
function deposit(address token, uint256 amount) external {
if (amount == 0) revert AmountZero();
// 节省: ~200 gas (运行时) + 部署时更省 bytecode
}
function liquidate(address user, address token, uint256 amount) external {
uint256 hf = getHealthFactor(user);
if (hf >= 1e18) revert PositionHealthy(hf);
// 自定义错误还能携带参数,更好 debug
}
四、Gas 优化效果测量
# 使用 forge snapshot 记录 Gas 基准
forge snapshot --snap .gas-snapshot-before
# 执行优化后再次快照
forge snapshot --snap .gas-snapshot-after
# 对比差异
forge snapshot --diff .gas-snapshot-before
# 示例输出:
test_deposit_happyPath() (gas: -3200, -4.2%)
test_borrow_happyPath() (gas: -5100, -5.8%)
test_liquidate_happyPath() (gas: -8700, -6.1%)
test_repay_fullRepayment() (gas: -4500, -5.0%)
关键要点总结
-
DeFi 测试的核心原则: 不仅测试"能不能工作",更要测试"攻击者能不能利用"。每个外部函数至少需要: happy path + 权限检查 + 边界条件 + 攻击场景。
-
Fuzz 测试是 DeFi 的必需品:
testFuzz_前缀让 Foundry 自动生成随机输入,覆盖手写测试无法想到的边界。用bound()限制输入范围。 -
Gas 优化的黄金法则: 先写正确的代码和完整的测试,再优化。使用
forge snapshot --diff量化每次优化的效果。 -
最有效的 Gas 优化手段排序: (1) 减少 SSTORE/SLOAD → (2) Slot Packing → (3) immutable/constant → (4) calldata 替代 memory → (5) unchecked → (6) 自定义错误。
-
测试覆盖率不等于安全性: 100% 行覆盖率不意味着合约安全。需要专门的攻击场景测试和后续的审计。
常见误区
-
误区: 先优化再测试 — 错!必须先有完整测试再优化。没有测试的优化等于盲目修改,可能引入 bug。
-
误区: unchecked 可以随处使用 — unchecked 块内溢出不会 revert。只在你确定不会溢出的地方使用(如循环计数器递增)。
-
误区: Gas 优化越多越好 — 过度优化会降低代码可读性和可维护性。只有高频调用的函数(deposit/borrow/swap)值得深度优化。
-
误区: 测试只需 happy path — DeFi 协议的绝大多数安全事件来自边界条件和攻击场景,而非正常流程中的 bug。
面试关联
Q: "你如何测试一个 DeFi 借贷协议?"
回答框架:
我会构建四层测试体系:
- 单元测试: 每个函数的正确性(deposit/withdraw/borrow/repay/liquidate)
- 边界测试: 零值、最大值、刚好触发阈值的场景
- 攻击测试: 重入、价格操纵、闪电贷、自我清算等已知攻击模式
- Fuzz 测试: 随机输入发现未预期的边界
此外,关键指标是"清算机制在极端市场条件下是否仍然正确"——这需要模拟价格暴跌、级联清算等场景。在上线前,还需要 Fork 主网进行集成测试。
Q: "你会用哪些 Gas 优化技术?"
按影响从大到小: (1) 减少存储操作(SSTORE 20000 gas)——合并多次写入、使用 events 替代存储;(2) Storage Slot Packing——将多个小变量打包到一个 32 bytes slot;(3) 使用 immutable/constant 避免运行时读取;(4) 使用 calldata 替代 memory 参数;(5) unchecked 块用于安全的算术。所有优化都必须有测试保障和量化数据。
参考资源
- Foundry Book - Testing — Foundry 测试官方文档
- Foundry Book - Gas Snapshots — Gas 快照对比
- Trail of Bits - Building Secure Smart Contracts — 安全测试实践
- Solidity Gas Optimizations — RareSkills Gas 优化指南
- Aave V3 Test Suite — 生产级 DeFi 测试参考