本文深入探讨了DeFi中价格操纵攻击的原理、危害和防范措施。文章详细解释了攻击者如何利用低流动性DEX池、单一预言机和闪电贷来操纵价格,从而导致不公平的清算、资金盗取和信任危机。此外,文章还提供了防范价格操纵攻击的安全代码示例、防御策略、最佳实践和测试工具。
在 DeFi 中,价格操纵攻击利用了智能合约如何从外部来源获取价格数据。这些攻击可能导致不公平的清算、窃取资金并损害信任。这是智能合约安全:Solodit 检查清单系列的第 7 章,重点关注 SOL-AM-PriceManipulation(预言机或市场操纵)。
为什么要关心?截至 2025 年 8 月 13 日,Ethereum 的权益证明具有约 4500 万 gas 的区块限制和约 12 秒的区块时间。此设置使闪电贷成为攻击者的强大工具,攻击贷款应用程序、DEX、稳定币和预测市场。
我们将简单地分解它:什么是攻击,为什么它很糟糕,它是如何运作的,真实示例,易受攻击与安全的代码,防御措施,最佳实践,测试等等。使用项目符号、表格、代码片段和图表以便于阅读。
智能合约需要准确的价格才能:
攻击者瞄准薄弱环节:
大风险:
攻击者会干扰价格数据源。主要方式:
常用策略:
Ethereum 的开放内存池和快速区块使攻击者可以在约 12 秒内完成此操作。
备受瞩目的攻击表明了危险。这是一个比较表:
此借贷合约使用单个 DEX 池 - 非常适合操纵。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
contract VulnerableLending {
address public priceFeed; // Single DEX pool
mapping(address => uint256) public collateral; // ETH staked
mapping(address => uint256) public debt; // USDC borrowed
uint256 public constant COLLATERAL_RATIO = 150; // 150% min
constructor(address _priceFeed) {
priceFeed = _priceFeed;
}
function depositCollateral() external payable {
collateral[msg.sender] += msg.value; // Add ETH
}
function borrow(uint256 amount) external {
uint256 price = getPriceFromFeed(); // Vulnerable spot price
uint256 collateralValue = (collateral[msg.sender] * price) / 1e18;
require(collateralValue >= (amount * COLLATERAL_RATIO) / 100, "Low collateral");
debt[msg.sender] += amount;
payable(msg.sender).transfer(amount); // Send USDC (simulated)
}
function liquidate(address user) external {
uint256 price = getPriceFromFeed(); // Manipulable
uint256 collateralValue = (collateral[user] * price) / 1e18;
require(collateralValue < (debt[user] * COLLATERAL_RATIO) / 100, "Not liquidatable");
uint256 seized = collateral[user];
collateral[user] = 0;
debt[user] = 0;
payable(msg.sender).transfer(seized); // Attacker gets ETH
}
function getPriceFromFeed() internal view returns (uint256) {
// Mock single DEX query
return 100 * 1e18; // Fixed for demo; real would query pool
}
}
为什么容易受到攻击?
结果:用户受到不公平的损失;协议失去信任。
使用聚合、TWAP、检查、延迟和熔断器修复它。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
contract SecureLending is ReentrancyGuard {
AggregatorV3Interface public primaryFeed; // Chainlink
AggregatorV3Interface public secondaryFeed; // Band Protocol
address public admin;
bool public paused;
mapping(address => uint256) public collateral;
mapping(address => uint256) public debt;
uint256 public constant COLLATERAL_RATIO = 150;
uint256 public constant TWAP_WINDOW = 1 hours;
uint256 public constant MAX_DEVIATION = 10; // %
uint256 public constant MIN_PRICE = 1e16; // 0.01 USDC/ETH
uint256 public constant MAX_PRICE = 1e22; // 10,000 USDC/ETH
uint256 public constant MAX_HISTORY = 100;
mapping(uint256 => uint256) public priceHistory;
uint256 public lastPriceUpdate;
mapping(address => uint256) public liquidationRequests;
modifier onlyAdmin() { require(msg.sender == admin, "Not admin"); _; }
modifier whenNotPaused() { require(!paused, "Paused"); _; }
constructor(address _primary, address _secondary) {
primaryFeed = AggregatorV3Interface(_primary);
secondaryFeed = AggregatorV3Interface(_secondary);
admin = msg.sender;
lastPriceUpdate = block.timestamp;
}
function depositCollateral() external payable whenNotPaused nonReentrant {
collateral[msg.sender] += msg.value;
}
function borrow(uint256 amount) external whenNotPaused nonReentrant {
updateTWAP();
uint256 price = getTWAP();
require(price >= MIN_PRICE && price <= MAX_PRICE, "Invalid price");
uint256 collateralValue = (collateral[msg.sender] * price) / 1e18;
require(collateralValue >= (amount * COLLATERAL_RATIO) / 100, "Low collateral");
debt[msg.sender] += amount;
payable(msg.sender).transfer(amount);
}
function requestLiquidation(address user) external whenNotPaused {
liquidationRequests[user] = block.timestamp; // Commit
}
function executeLiquidation(address user) external whenNotPaused nonReentrant {
require(block.timestamp >= liquidationRequests[user] + 1 hours, "Too soon"); // Reveal delay
updateTWAP();
uint256 price = getTWAP();
uint256 collateralValue = (collateral[user] * price) / 1e18;
require(collateralValue < (debt[user] * COLLATERAL_RATIO) / 100, "Not liquidatable");
uint256 seized = collateral[user];
collateral[user] = 0;
debt[user] = 0;
payable(msg.sender).transfer(seized);
}
function updateTWAP() internal {
(, int256 p1,, uint256 t1,) = primaryFeed.latestRoundData();
(, int256 p2,, uint256 t2,) = secondaryFeed.latestRoundData();
require(p1 > 0 && p2 > 0, "Invalid");
require(t1 >= lastPriceUpdate && t2 >= lastPriceUpdate, "Stale");
uint256 avg = (uint256(p1) + uint256(p2)) / 2;
require(avg >= MIN_PRICE && avg <= MAX_PRICE, "Bounds error");
require(validatePrice(avg), "Deviation high");
priceHistory[block.timestamp] = avg;
if (block.timestamp > lastPriceUpdate + TWAP_WINDOW / MAX_HISTORY) delete priceHistory[lastPriceUpdate - TWAP_WINDOW];
lastPriceUpdate = block.timestamp;
}
function getTWAP() internal view returns (uint256) {
uint256 start = block.timestamp > TWAP_WINDOW ? block.timestamp - TWAP_WINDOW : 0;
uint256 total = 0; uint256 count = 0;
for (uint t = start; t <= block.timestamp; t++) {
if (priceHistory[t] > 0) { total += priceHistory[t]; count++; }
}
require(count > 0, "No history");
return total / count;
}
function validatePrice(uint256 newPrice) internal view returns (bool) {
uint256 last = priceHistory[lastPriceUpdate];
if (last == 0) return true;
uint256 dev = newPrice > last ? newPrice - last : last - newPrice;
return (dev * 100 / last) <= MAX_DEVIATION;
}
function pause() external onlyAdmin { paused = true; }
receive() external payable { revert("No direct ETH"); }
}
它如何保护:
使用这些来构建强大的保护。每个都带有解释、优缺点和代码。
function getPrice() view returns (uint256) {
(, int256 p1,,,) = primaryFeed.latestRoundData();
(, int256 p2,,,) = secondaryFeed.latestRoundData();
return (uint256(p1) + uint256(p2)) / 2; // Average
}
function getTWAP() view returns (uint256) {
uint256 total = 0; uint256 count = 0;
uint256 start = block.timestamp - TWAP_WINDOW;
for (uint t = start; t <= block.timestamp; t++) {
if (priceHistory[t] > 0) { total += priceHistory[t]; count++; }
}
return count > 0 ? total / count : 0;
}
function validate(uint256 newPrice) view returns (bool) {
uint256 last = priceHistory[lastUpdate];
uint256 dev = newPrice > last ? newPrice - last : last - newPrice;
return (dev * 100 / last) <= MAX_DEVIATION && newPrice >= MIN_PRICE && newPrice <= MAX_PRICE;
}
function pause() external onlyAdmin { paused = true; }
modifier notPaused() { require(!paused, "Paused"); _; }
遵循此清单以获得可靠的防御:
像这样进行测试:
it("rejects bad prices", async () => {
await attacker.skewPrice(); // Mock flash
await expect(contract.borrow(100)).to.be.revertedWith("Invalid price");
});
工具(2025 年更新):
价格操纵是隐蔽的,但可以击败。使用多重预言机、TWAP、检查、延迟和熔断器进行保护。这会创建公平、可信的合约。接下来:使用 UUPS 代理的可升级性风险。掌握此内容以确保 DeFi 安全!
- 原文链接: medium.com/@ankitacode11...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!