Разработка системы мониторинга подозрительных транзакций

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1Все 1306 услуг
Разработка системы мониторинга подозрительных транзакций
Сложный
~1-2 недели
Часто задаваемые вопросы

Направления блокчейн-разработки

Этапы блокчейн-разработки

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1306
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1218
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    920
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1147
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    610
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    885

Настройка автоматического тестирования смарт-контрактов

Автоматическое тестирование — это то, что отделяет контракты с $10 тыс. TVL от контрактов с $100 млн TVL. Не потому что аудит заменяет тесты, а потому что без тестового покрытия аудит обходится в 3–5 раз дороже и находит меньше проблем: аудитор тратит время на понимание базового поведения контракта вместо глубокого анализа edge cases.

Хорошая тестовая инфраструктура — это Foundry для быстрых unit/fuzz тестов + coverage отчёты + CI pipeline, который не даёт мёрджить код с падающими тестами.

Foundry как основной инструмент

Foundry — современный стандарт для тестирования Solidity. Тесты пишутся на Solidity (не JavaScript), что даёт прямой доступ к EVM internals без ABI-слоя. Компиляция и запуск тестов занимают секунды против минут у Hardhat для больших проектов.

# Установка
curl -L https://foundry.paradigm.xyz | bash
foundryup

# Структура проекта
forge init my-project
# src/       - контракты
# test/       - тесты
# script/    - деплой скрипты
# lib/       - зависимости (git submodules)

Базовая структура теста

// test/Token.t.sol
pragma solidity ^0.8.20;

import {Test, console} from "forge-std/Test.sol";
import {MyToken} from "../src/MyToken.sol";

contract TokenTest is Test {
    MyToken token;
    address alice = makeAddr("alice"); // детерминированный адрес из label
    address bob = makeAddr("bob");
    
    function setUp() public {
        token = new MyToken(1_000_000e18); // деплой перед каждым тестом
        token.transfer(alice, 1000e18);
    }
    
    function test_transfer_success() public {
        vm.prank(alice); // следующий вызов — от alice
        token.transfer(bob, 100e18);
        
        assertEq(token.balanceOf(bob), 100e18);
        assertEq(token.balanceOf(alice), 900e18);
    }
    
    function test_transfer_revertIfInsufficientBalance() public {
        vm.prank(alice);
        vm.expectRevert("ERC20: transfer amount exceeds balance");
        token.transfer(bob, 2000e18); // больше чем у alice
    }
}

vm — это cheat code interface от Foundry (forge-std/Test.sol). Ключевые cheat codes:

  • vm.prank(addr) — следующий вызов от addr
  • vm.startPrank(addr) / vm.stopPrank() — все последующие вызовы от addr
  • vm.warp(timestamp) — установить block.timestamp
  • vm.roll(blockNumber) — установить block.number
  • vm.deal(addr, amount) — установить ETH баланс
  • vm.expectEmit(...) — ожидать конкретное событие
  • vm.expectRevert(...) — ожидать revert с сообщением

Fuzz-тестирование

Fuzz-тестирование — автоматическая генерация тест-кейсов. Foundry запускает функцию тысячи раз с разными случайными входными данными, ища нарушение инварианта.

// Fuzz test: функция с параметрами = fuzz test
function testFuzz_deposit_neverOverflows(uint256 amount) public {
    // bound ограничивает диапазон входных данных
    amount = bound(amount, 1, 1_000_000e18);
    
    deal(address(token), alice, amount);
    
    vm.prank(alice);
    token.approve(address(vault), amount);
    
    vm.prank(alice);
    vault.deposit(amount);
    
    // Инвариант: shares всегда > 0 при deposit > 0
    assertGt(vault.balanceOf(alice), 0);
    // Инвариант: total assets растёт на amount
    assertEq(vault.totalAssets(), amount);
}

Foundry по умолчанию запускает 256 итераций. В CI рекомендуется 1000–10000 для critical функций:

# foundry.toml
[fuzz]
runs = 1000
seed = "0x1" # воспроизводимость при одинаковом seed

bound(amount, min, max) — крайне важная функция. Без неё fuzz генерирует много нулей и максимальных значений uint256, которые часто fail по очевидным причинам, игнорируя интересный middle range.

Invariant-тестирование

Invariant тесты — более мощный инструмент. Foundry случайно вызывает любые функции контракта в любом порядке и проверяет что инвариант никогда не нарушается.

contract VaultInvariantTest is Test {
    Vault vault;
    ERC20Mock token;
    VaultHandler handler;
    
    function setUp() public {
        token = new ERC20Mock();
        vault = new Vault(address(token));
        handler = new VaultHandler(vault, token);
        
        // Указываем Foundry работать только через handler
        targetContract(address(handler));
    }
    
    // Этот тест запускается после каждой случайной последовательности действий
    function invariant_totalAssetsMatchBalances() public {
        assertEq(
            vault.totalAssets(),
            token.balanceOf(address(vault)),
            "Total assets mismatch"
        );
    }
    
    function invariant_sharePriceNeverDecreases() public {
        // Если vault не потерял средства, цена share не должна падать
        assertGe(vault.pricePerShare(), handler.lastRecordedPrice());
    }
}

// Handler контролирует допустимые действия
contract VaultHandler is Test {
    Vault vault;
    ERC20Mock token;
    uint256 public lastRecordedPrice;
    
    function deposit(uint256 amount) external {
        amount = bound(amount, 1, 1e30);
        token.mint(address(this), amount);
        token.approve(address(vault), amount);
        vault.deposit(amount, address(this));
        lastRecordedPrice = vault.pricePerShare();
    }
    
    function withdraw(uint256 shares) external {
        shares = bound(shares, 0, vault.balanceOf(address(this)));
        if (shares > 0) vault.withdraw(shares, address(this), address(this));
    }
}

Invariant тесты находят сложные межфункциональные баги, которые unit тесты пропускают. ERC-4626 vault инварианты (totalAssets = баланс контракта, share price монотонна) — классический пример.

Fork тестирование

Fork тесты работают с реальным state mainnet, что позволяет тестировать интеграцию с реальными Uniswap, Aave, Chainlink.

contract ForkTest is Test {
    uint256 mainnetFork;
    
    address constant UNISWAP_V3_ROUTER = 0xE592427A0AEce92De3Edee1F18E0157C05861564;
    address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
    address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    
    function setUp() public {
        // Форк mainnet на конкретном блоке (детерминированность)
        mainnetFork = vm.createFork(vm.envString("ETH_RPC_URL"), 19_500_000);
        vm.selectFork(mainnetFork);
    }
    
    function test_swapOnFork() public {
        address whale = 0x40ec5B33f54e0E8A33A975908C5BA1c14e5BbbDf; // USDC whale
        vm.prank(whale);
        IERC20(USDC).transfer(address(this), 10_000e6);
        
        IERC20(USDC).approve(UNISWAP_V3_ROUTER, 10_000e6);
        
        ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
            tokenIn: USDC,
            tokenOut: WETH,
            fee: 500,
            recipient: address(this),
            deadline: block.timestamp + 60,
            amountIn: 10_000e6,
            amountOutMinimum: 0,
            sqrtPriceLimitX96: 0
        });
        
        uint256 amountOut = ISwapRouter(UNISWAP_V3_ROUTER).exactInputSingle(params);
        assertGt(amountOut, 0);
    }
}

RPC кэширование: Foundry кэширует RPC ответы локально (~/.foundry/cache/). Повторный запуск fork тестов на том же блоке не делает новых RPC запросов.

Coverage и качественные метрики

# Генерация coverage отчёта
forge coverage --report lcov

# Подробный отчёт в terminal
forge coverage --report summary

Coverage показывает % строк/веток/функций охваченных тестами. Целевые показатели для production контрактов:

Тип контракта Line coverage Branch coverage
Core logic (vault, AMM) >95% >90%
Peripheral (router, helper) >85% >75%
Admin/governance >90% >85%

Но coverage — не единственная метрика. 100% line coverage при отсутствии assertion-ов в тестах ничего не гарантирует. Нужно считать количество assert* и vm.expectRevert на функцию.

CI/CD интеграция

# .github/workflows/test.yml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive

      - name: Install Foundry
        uses: foundry-rs/foundry-toolchain@v1

      - name: Run tests
        run: forge test --fork-url ${{ secrets.ETH_RPC_URL }} -vvv
        env:
          ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }}

      - name: Check coverage
        run: |
          forge coverage --report summary | tee coverage.txt
          # Fail если line coverage < 90%
          python3 -c "
          import re, sys
          with open('coverage.txt') as f:
              content = f.read()
          match = re.search(r'Lines.*?(\d+\.\d+)%', content)
          if match and float(match.group(1)) < 90:
              print(f'Coverage {match.group(1)}% < 90%')
              sys.exit(1)
          "

Дополнительные инструменты

Slither (статический анализ, Trail of Bits): находит известные паттерны уязвимостей. Запускается за секунды, интегрируется в CI.

pip install slither-analyzer
slither src/ --exclude-dependencies

Echidna (property-based fuzzer, Trail of Bits): более мощный fuzzer для специфических инвариантов. Написан на Haskell, требует конфигурации на Solidity.

Halmos (formal verification): symbolically проверяет все возможные пути исполнения. Находит уязвимости, которые statistical fuzzing пропускает. Требует значительных вычислительных ресурсов.

Оптимальная связка для большинства проектов: Foundry (unit + fuzz + invariant) + Slither (CI) + coverage gate. Echidna и Halmos — для критических компонентов после основной разработки.

Настройка полной тестовой инфраструктуры с CI: 1–2 недели для проекта среднего размера. Написание comprehensive тестов для существующей codebase — 3–6 недель в зависимости от размера и сложности.