Loading...
Loading...
// ✅ ЗАЩИЩЕННАЯ функция обмена с slippage protectioncontract ProtectedDEX { function swapExactETHForTokens( uint256 amountOutMin, // 🔑 КЛЮЧЕВОЙ параметр защиты! address[] calldata path, address to, uint256 deadline ) external payable { // Получаем количество токенов за ETH uint256[] memory amounts = getAmountsOut(msg.value, path); uint256 actualAmountOut = amounts[amounts.length - 1];
// 🛡️ ЗАЩИТА: проверяем что получили достаточно токенов require( actualAmountOut >= amountOutMin, "SLIPPAGE_TOO_HIGH: получили меньше токенов чем ожидали" );
// Выполняем обмен только если условие выполнено _swap(amounts, path, to); }}// ❌ ОПАСНО: Алекс не устанавливает минимумdex.swapExactETHForTokens( 0, // amountOutMin = 0 - ЛЮБОЕ количество токенов! [ETH, DOGE], alex, deadline);
// Результат сендвич-атаки:// - Ожидал: 4975 DOGE// - Получил: 4900 DOGE (потеря 75 DOGE)// - Транзакция ПРОШЛА, Алекс потерял деньги!// ✅ БЕЗОПАСНО: Алекс устанавливает минимумuint256 expectedDOGE = 4975;uint256 slippageTolerance = 1; // 1%uint256 minDOGE = expectedDOGE * 99 / 100; // = 4925 DOGE
dex.swapExactETHForTokens( minDOGE, // 🛡️ Минимум 4925 DOGE! [ETH, DOGE], alex, deadline);
// Результат сендвич-атаки:// - Бот пытается дать Алексу 4900 DOGE// - 4900 < 4925 (минимум)// - Транзакция ОТКАТЫВАЕТСЯ!// - Алекс НЕ потерял деньги1. Пользователь отправляет транзакцию2. Транзакция попадает в ПУБЛИЧНЫЙ мемпул3. 🚨 ВСЕ БОТЫ видят транзакцию!4. Боты анализируют выгоду5. Боты отправляют свои транзакции с higher gas6. Майнер включает все транзакции в блок (сначала ботов)7. Пользователь получает плохую цену1. Пользователь отправляет транзакцию через приватный релей2. Транзакция НЕ попадает в публичный мемпул3. ✅ Боты НЕ видят транзакцию!4. Приватный релей передает транзакцию напрямую майнеру5. Майнер включает транзакцию в блок6. Пользователь получает честную цену// ❌ ОПАСНАЯ биржа Алекса - потеряла $50,000!contract AlexVulnerableExchange { mapping(address => mapping(address => uint256)) public balances; // token => user => amount
function deposit(address token, uint256 amount) public { // Принимаем ЛЮБОЙ токен без проверки! IERC20(token).transferFrom(msg.sender, address(this), amount); balances[token][msg.sender] += amount; }
function withdraw(address token, uint256 amount) public { require(balances[token][msg.sender] >= amount, "Недостаточно средств");
balances[token][msg.sender] -= amount; IERC20(token).transfer(msg.sender, amount); }
function swap( address tokenFrom, address tokenTo, uint256 amountFrom, uint256 amountTo ) public { require(balances[tokenFrom][msg.sender] >= amountFrom, "Недостаточно средств");
// Снимаем исходный токен balances[tokenFrom][msg.sender] -= amountFrom;
// Начисляем целевой токен balances[tokenTo][msg.sender] += amountTo;
// Переводим токены IERC20(tokenFrom).transfer(address(this), amountFrom); // ❌ ПРОБЛЕМА! IERC20(tokenTo).transferFrom(address(this), msg.sender, amountTo); }}// ⚔️ ОТРАВЛЕННЫЙ токен хакера - выглядит как USDC!contract PoisonedUSDC { string public name = "USD Coin"; // Копирует название! string public symbol = "USDC"; // Копирует символ! uint8 public decimals = 6; // Копирует decimals! uint256 public totalSupply = 1000000 * 10**6;
mapping(address => uint256) private _balances; mapping(address => mapping(address => uint256)) private _allowances;
address public owner; address public targetExchange; // Биржа которую атакуем
constructor(address _targetExchange) { owner = msg.sender; targetExchange = _targetExchange; _balances[msg.sender] = totalSupply; }
// ⚡ ЛОВУШКА #1: Ложный баланс function balanceOf(address account) public view returns (uint256) { if (account == targetExchange) { // Говорим бирже что у неё много токенов! return 1000000 * 10**6; } return _balances[account]; }
// ⚡ ЛОВУШКА #2: Ложное разрешение function allowance(address owner, address spender) public view returns (uint256) { if (spender == targetExchange) { // Биржа думает что может тратить много токенов! return 1000000 * 10**6; } return _allowances[owner][spender]; }
// ⚡ ЛОВУШКА #3: Притворяемся что transfer прошёл успешно function transfer(address to, uint256 amount) public returns (bool) { if (msg.sender == targetExchange && to == owner) { // Биржа думает что отправила токены хакеру! return true; // ЛОЖ! Токены остались на бирже } return _transfer(msg.sender, to, amount); }
// ⚡ ЛОВУШКА #4: Reentrancy атака через transferFrom function transferFrom(address from, address to, uint256 amount) public returns (bool) { if (from == targetExchange) { // При каждом transferFrom делаем reentrancy! if (targetExchange.balance > 0) { IVulnerableExchange(targetExchange).emergencyWithdraw(); } return true; // Притворяемся что transfer прошёл } return _transferFrom(from, to, amount); }
function _transfer(address from, address to, uint256 amount) internal returns (bool) { require(_balances[from] >= amount, "Insufficient balance"); _balances[from] -= amount; _balances[to] += amount; return true; }
function _transferFrom(address from, address to, uint256 amount) internal returns (bool) { require(_allowances[from][msg.sender] >= amount, "Insufficient allowance"); require(_balances[from] >= amount, "Insufficient balance");
_allowances[from][msg.sender] -= amount; _balances[from] -= amount; _balances[to] += amount; return true; }}// ✅ БЕЗОПАСНАЯ биржа с белым спискомcontract AlexSecureExchange { mapping(address => bool) public allowedTokens; mapping(address => string) public tokenNames; address public owner;
modifier onlyOwner() { require(msg.sender == owner, "Только владелец"); _; }
modifier onlyAllowedToken(address token) { require(allowedTokens[token], "Токен не разрешен для торговли"); _; }
function addAllowedToken( address token, string memory name ) public onlyOwner { // Проводим проверку токена перед добавлением require(_isValidToken(token), "Токен не прошел проверку");
allowedTokens[token] = true; tokenNames[token] = name; }
function removeAllowedToken(address token) public onlyOwner { allowedTokens[token] = false; }
function deposit(address token, uint256 amount) public onlyAllowedToken(token) { IERC20(token).transferFrom(msg.sender, address(this), amount); balances[token][msg.sender] += amount; }
function swap( address tokenFrom, address tokenTo, uint256 amountFrom, uint256 amountTo ) public onlyAllowedToken(tokenFrom) onlyAllowedToken(tokenTo) { // Теперь безопасно - оба токена проверены! require(balances[tokenFrom][msg.sender] >= amountFrom, "Недостаточно средств");
balances[tokenFrom][msg.sender] -= amountFrom; balances[tokenTo][msg.sender] += amountTo; }
function _isValidToken(address token) internal view returns (bool) { // Проверяем что контракт действительно токен try IERC20(token).totalSupply() returns (uint256) { return true; } catch { return false; } }}// ❌ УЯЗВИМЫЙ код, основанный на балансеcontract VulnerableRewards { mapping(address => uint256) public userShares;
function deposit() public payable { userShares[msg.sender] += msg.value; }
function claimRewards() public { uint256 rewards = userShares[msg.sender] / totalShares; userShares[msg.sender] = 0; payable(msg.sender).transfer(rewards); }}
// ⚔️ Атакующий контрактcontract BalanceAttacker { function attack(address target) public payable { // Отправляем ETH через selfdestruct, минуя fallback selfdestruct(payable(target)); }}address(this).balance// ✅ БЕЗОПАСНАЯ версия с отслеживанием балансаcontract SecureRewards { mapping(address => uint256) public userShares; uint256 public totalShares;
function deposit() public payable { totalShares += msg.value; // Учитываем только легальные депозиты }
function claimRewards() public { uint256 rewards = userShares[msg.sender] / totalShares; userShares[msg.sender] = 0; payable(msg.sender).transfer(rewards); }}// ❌ ПОТЕРЯ ТОЧНОСТИcontract VulnerableStaking { uint256 public totalStaked; uint256 public totalRewards = 100 ether;
function calculateReward(address user, uint256 userStake) public view returns (uint256) { // При малых долях результат может быть 0! return (userStake * totalRewards) / totalStaked; }}// ✅ ВЫСОКАЯ ТОЧНОСТЬ РАСЧЕТОВcontract SecureStaking { uint256 public constant PRECISION = 1e18; uint256 public totalStaked; uint256 public totalRewards = 100 ether;
function calculateReward(address user, uint256 userStake) public view returns (uint256) { // Умножаем на PRECISION для точности, потом делим return (userStake * totalRewards * PRECISION) / totalStaked / PRECISION; }}onlyOwneronlyOwnermsg.sender вместо tx.originaddress(this).balancepause)import "@openzeppelin/contracts/access/Ownable.sol";import "@openzeppelin/contracts/security/Pausable.sol";import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SecureWallet is Ownable, Pausable, ReentrancyGuard { // Уже есть встроенная защита!}contract MultiSigSecurity { address[] public owners; mapping(address => bool) public isOwner; uint256 public required;
struct Transaction { address to; uint256 value; bool executed; uint256 confirmations; }
Transaction[] public transactions; mapping(uint256 => mapping(address => bool)) public confirmations;
modifier onlyOwner() { require(isOwner[msg.sender], "Не владелец"); _; }
function submitTransaction(address to, uint256 value) public onlyOwner { uint256 txId = transactions.length; transactions.push(Transaction({ to: to, value: value, executed: false, confirmations: 0 })); confirmTransaction(txId); }
function confirmTransaction(uint256 txId) public onlyOwner { require(!confirmations[txId][msg.sender], "Уже подтверждено");
confirmations[txId][msg.sender] = true; transactions[txId].confirmations++;
if (transactions[txId].confirmations >= required) { executeTransaction(txId); } }
function executeTransaction(uint256 txId) internal { Transaction storage txn = transactions[txId]; require(!txn.executed, "Уже выполнено"); require(txn.confirmations >= required, "Недостаточно подтверждений");
txn.executed = true; (bool success, ) = txn.to.call{value: txn.value}(""); require(success, "Выполнение не удалось"); }}

