Главная » Блог » Как за вечер собрать ваш первый смарт-контракт с Foundry

Как за вечер собрать ваш первый смарт-контракт с Foundry

Опубликовано:

Обновлено:

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

Смарт-контракт — это маленькая программа, которая живет в блокчейне и автоматически выполняет правила: кто и что может делать, когда и на каких условиях. Сильные стороны этой технологии — прозрачность (код видно), предсказуемость (выполняется одинаково для всех) и отсутствие «человеческого фактора» в момент исполнения. На этом строят токены, платежные схемы, игры с предметами, DAO, аукционы — все, где важно доверять правилам, а не людям.

Если вам нужно писать контракты, проверять их или быстро прототипировать идеи, начнем с минимального набора — инструментов и настроек, которые закрывают весь цикл «идея → код → проверка → запуск в песочнице» без лишней установки и рисков. В него входят:

  • редактор кода (VS Code / VSCodium) + расширение Solidity (Nomic Foundation);
  • удобная среда (Linux/macOS или Windows с WSL);
  • фреймворк Foundry;
  • автоматические тесты.

С этой связкой вы за вечер сможете:

  • развернуть локальную сеть или форк мейннета;
  • запустить юнит- и fuzz-тесты;
  • добавить базовые инварианты (простые неизменные правила контракта, которые Foundry автоматически проверяет при любых последовательностях действий);

И все это вы сделаете без реальных денег и риска что-либо сломать.

Такой старт уже дает практическую пользу: можно собрать прототип токена или аукциона, воспроизвести баг-репорт, оценить безопасность логики и, главное, говорить с техкомандой на одном языке.

Рабочая среда Foundry: от нуля до первого теста

Здесь мы шаг-за-шагом попробуем создать свой первый смарт-контракт через фреймворк Foundry: инициализируем проект, напишем простые функции, запустим тесты и, при желании, поднимем локальную сеть/форк для экспериментов.

Мы берем Foundry как базовую среду, потому что это самый короткий путь от идеи до проверенного контракта: очень быстрые сборка и тесты, встроенные fuzz и инварианты, газ-репорт из коробки и простые деплой-скрипты — все без лишних Node/Python-зависимостей. Ниже — сравнение с альтернативами, чтобы увидеть контекст выбора.

Какие задачи закрывает Foundry:

  • Быстрый цикл «написал → проверил». Сборка и тесты запускаются одной командой — сразу видно «зеленый/красный».
  • Автоматические проверки «по краям». Встроенный fuzz гоняет ваши функции на сотнях входов, а инварианты следят за правилами системы при длинных сценариях.
  • Безопасные эксперименты. Локальная сеть (Anvil) имитирует блокчейн у вас «под рукой», а форк сети позволяет воспроизводить поведение, как в живой, реальной среде, без риска.
  • Измерение стоимости. Газ-репорт показывает, какие функции «дорогие» и где вы теряете эффективность.
  • Деплой и скрипты. Простые команды для развертывания и вызовов контрактов — без сложных настроек.

Из чего состоит Foundry?

Стек включает в себя 4 утилиты, которые легко запомнить:

  • forge — сборка, тестирование, деплой. «Рабочая лошадка» вашего проекта;
  • anvil — локальный узел и форки реальных сетей (Mainnet/Sepolia и т. д.);
  • cast — удобные вызовы к контрактам и RPC (чтение состояния, отправка транзакции);
  • chisel — интерактивная «песочница» для быстрой проверки кусочков Solidity.

Типичный рабочий процесс с Foundry

  1. Создаете проект и пишете контракт.
  2. Пишете тесты: сначала обычные, затем добавляете fuzz и, при необходимости, инварианты.
  3. Запускаете локальную сеть (или форк) и проигрываете сценарии «как в реальной сети».
  4. Смотрите отчет по газу, оптимизируете и развертываете (деплоите) туда, где нужно: локально, в тестнет или прод.

Порог входа — низкий. Начать можно без установки на свой компьютер — прямо в браузере, в облачной среде (например, GitHub Codespaces). Там же вы запустите forge test, поднимете anvil, выполните cast call/send. Когда будет удобно — перенесете тот же проект локально.

Мини-словарь

  • Fuzz-тест — тест, который автоматически подставляет множество случайных входов, чтобы поймать неожиданные случаи.
  • Инвариант — правило, которое всегда должно выполняться при любых последовательностях действий (например: «баланс не уходит в минус», «лимит не превышается»).
  • Форк сети — локальная копия состояния выбранной сети «на текущем блоке», где можно безопасно повторять «реальные» сценарии.

Зачем вам это нужно:

  • Разработчику на старте: быстрое освоение практики без «боли установки», уверенность за счет тестов и fuzz.
  • Исследователю безопасности: воспроизведение баг-репортов на форке, проверка свойств через инварианты.
  • Продакту/аналитику: возможность проверить правила «на деле» и говорить с техкомандой на одном языке.

Дальше пойдем по шагам: откроем облачную среду, установим Foundry, напишем простой контракт, добавим тесты, fuzz и инвариант, поднимем anvil (и при желании — форк), затем развернем контракт и вызовем его методы. Все — с понятными ожиданиями «что сделать» и «что получить на выходе».

Развертывание смарт-контракта через Foundry

Чтобы не зависать в теории, начнем с практики в облаке. Проведем короткий эксперимент и соберем «песочницу» для смарт-контрактов прямо в браузере (GitHub Codespaces). К финалу мини-эксперимента у вас будет готовое облачное рабочее место:

  • редактор VS Code в браузере, Linux-окружение и установленный Foundry;
  • созданный проект, проходящий базовые тесты;
  • запущенный локальный узел Anvil или форк основной сети (опционально);
  • плюс две автоматические проверки — fuzz-тест (гоняет функцию на разных входах) и простой инвариант (правило, которое всегда должно выполняться).

С таким комплектом вы соберете прототип (токен/ NFT/ аукцион), проверите доступы, смоделируете эскроу и таймлоки, поймаете ошибки до ревью, воспроизведете баг на форке и поймете, что «дорого» по газу. А еще — сможете обсуждать все с командой на языке тестов. Этого хватит, чтобы уверенно двигаться дальше.

Пошаговая инструкция по работе с Foundry

Оптимальный старт — запустить Foundry в облачной среде GitHub Codespaces: работаем прямо в браузере, без локальной установки, и сразу переходим к практике.

0) Подготовьте Codespace

Что сделать

  1. Переходим на GitHub (https://github.com/features/codespaces) и регистрируемся.
  2. Создаем репозиторий → Create repository 
  1. Называем репозиторий именем проекта, например, hello-foundry.
  1. Жмем Create repository
  1. Переходим во вкладку Code
  1. В репозитории создаем файл  → Creating a new file
  1. Называем его README.md → добавляем любой текст, например, Commit

Подтверждаем

  1. Жмем Code → Codespaces → Create codespace on main
  1. Дождаться открытия VS Code в браузере → Terminal → New Terminal.

Что должно получиться: 

  • Открылся VS Code в браузере, внизу есть терминал со строкой вида:

codespace@…:/workspaces/hello-foundry$

1. Устанавливаем Foundry

Отправляем в терминал Codespaces:

export PATH=»$HOME/.foundry/bin:$PATH» \

&& curl -L https://foundry.paradigm.xyz | bash \

&& source ~/.bashrc \

&& foundryup

forge —version && anvil —version && cast —version && chisel —version

Что должно получиться:

  • Печатаются версии всех утилит (например, forge Version: 1.3.1-stable). 

Все инструменты готовы.

2. Создаем проект Foundry

Отправляем в терминал forge init hello — это команда Foundry, которая создает новый проект в папке hello с готовой структурой и шаблонами:

forge init hello

cd hello

forge test

Что должно получиться:

В консоли несколько строк PASS … и итог ok. X passed; 0 failed. Базовые тесты из шаблона прошли.

3. Добавляем контракт Counter

Создаем файл src/Counter.sol автоматически — прямо из терминала Codespaces.

mkdir -p src && cat > src/Counter.sol <<‘SOL’

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.13;

contract Counter {

    uint256 public number;

    function setNumber(uint256 newNumber) public { number = newNumber; }

    function increment() public { number++; }

}

SOL

Что должно получиться:

  • Появится файл src/Counter.sol с нужным содержимым.
  • Проверка: ls src и forge build (сборка без ошибок).

Вот, что должно отображаться в содержимом файла Counter.sol

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.13;

contract Counter {

    uint256 public number;

    function setNumber(uint256 newNumber) public { number = newNumber; }

    function increment() public { number++; }

}

4. Пишем тесты

Создаем файл test/Counter.t.sol одной командой, которую отправляем в терминал:

mkdir -p test && cat > test/Counter.t.sol <<‘SOL’

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.13;

import «forge-std/Test.sol»;

import «../src/Counter.sol»;

contract CounterTest is Test {

    Counter counter;

    function setUp() public {

        counter = new Counter();

    }

    function test_SetNumber() public {

        counter.setNumber(5);

        assertEq(counter.number(), 5);

    }

    function test_Increment() public {

        counter.increment();

        assertEq(counter.number(), 1);

    }

    // Fuzz: прогон на множестве входов

    function testFuzz_SetNumber(uint256 x) public {

        vm.assume(x < 1e18);

        counter.setNumber(x);

        assertEq(counter.number(), x);

    }

}

SOL

Файл Counter.t.sol должен содержать следующие значения: 

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.13;

import «forge-std/Test.sol»;

import «../src/Counter.sol»;

contract CounterTest is Test {

    Counter counter;

    function setUp() public {

        counter = new Counter();

    }

    function test_SetNumber() public {

        counter.setNumber(5);

        assertEq(counter.number(), 5);

    }

    function test_Increment() public {

        counter.increment();

        assertEq(counter.number(), 1);

    }

    // Fuzz: прогон на множестве входов

    function testFuzz_SetNumber(uint256 x) public {

        vm.assume(x < 1e18);

        counter.setNumber(x);

        assertEq(counter.number(), x);

    }

}

Запустите тест:

forge test

Что должно получиться:

  • Три PASS (обычные тесты + fuzz), итог ok. 3 passed; 0 failed.

5. Поднимаем локальную сеть (Anvil)

Открываем вторую вкладку терминала.

Запускаем:

source ~/.bashrc

export PATH=»$HOME/.foundry/bin:$PATH»

anvil —version

Что должно получиться:

  • Терминал выдаст версию anvil ….

Запускаем в терминале:

anvil

(оставьте это окно открытым; для остановки — Ctrl+C)

Что должно получиться

  • В логе видно Listening on 127.0.0.1:8545 и список тестовых аккаунтов с приватными ключами.
  • В Codespaces во вкладке Ports появился порт 8545.

Это «песочница»: можно деплоить и вызывать контракты без риска и без реальных средств.

6. Задаем переменные окружения для деплоя: RPC и API-ключ

  • RPC_URL — куда отправлять команды (локальный Anvil или форк/тестнет).
  • PRIVATE_KEY — нужен для транзакций (деплой, cast send). Для локального Anvil используем любой ключ из его лога — там уже есть тестовый баланс.

Мы будем работать с локальным Anvil (из шага 5). Отправляем в терминал

export RPC_URL=http://127.0.0.1:8545

export PRIVATE_KEY=0xВАШ_КЛЮЧ_ИЗ_ЛОГА_ANVIL

Этот код редактируем в следующем порядке: одну строку оставляем как есть, вторую — заменяем.

  1. RPC_URL не редактируем для локального Anvil:

export RPC_URL=http://127.0.0.1:8545

  1. 0xВАШ_КЛЮЧ_ИЗ_ЛОГА_ANVIL заменяем на реальный ключ из вывода Anvil (любая строка “Private Key …” из окна, где запущен anvil):

export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80

Что должно получиться:

Чтобы убедиться, что все работает, отправляем последовательно 2 команды:

  1. echo $RPC_URL
  2. echo ${PRIVATE_KEY:0:12}…

В первом случае должно показать http://127.0.0.1:8545, во втором — первые символы ключа.

Если делаете форк реальной сети, то меняете RPC_URL на URL провайдера (регистрируемся на эти сервисах Infura/Alchemy и получаем свой API):

export RPC_URL=https://sepolia.infura.io/v3/<API_KEY>

  • PRIVATE_KEY — ваш отдельный тестовый ключ (не из Anvil), с тестовым балансом.

Полезно помнить:

  • Переменные действуют только в этой вкладке терминала. Откроете новую — их там нет. Повторите export … или сохраните значения в .env и подгружайте его:

# один раз создаем .env

echo ‘RPC_URL=http://127.0.0.1:8545’   > .env

echo ‘PRIVATE_KEY=0x…’              >> .env

# в каждой новой вкладке

source .env

  • Секреты нельзя коммитить. Добавьте .env в .gitignore, чтобы он не попал в репозиторий:

echo ‘.env’ >> .gitignore

  • Можно обойтись без переменных. Передавайте значения прямо флагами в команду: 

forge create src/Counter.sol:Counter \

  —rpc-url http://127.0.0.1:8545 \

  —private-key 0x… \

  —broadcast

печатает http://127.0.0.1:8545. Теперь forge create / cast send будут использовать эти значения по умолчанию.

  • Быстрая проверка, что экспорт сработал (если используете переменные):

echo $RPC_URL

echo ${PRIVATE_KEY:0:12}…

7. Разворачиваем контракт

В рабочей вкладке терминала (не там, где крутится anvil) выполните:

forge create src/Counter.sol:Counter \

  —rpc-url $RPC_URL \

  —private-key $PRIVATE_KEY \

  —broadcast

В выводе появятся строки:

Deployed to: 0x… и Transaction hash: 0x….

Сохраняем адрес:

export COUNTER=0x<адрес_из_вывода>

В строке 0x<адрес_из_вывода> используйте адрес из строки Deployed to: — это адрес развернутого контракта.

Если деплоите на форке: убедитесь, что в отдельной вкладке запущен anvil —fork-url «$RPC_URL».

8. Проверяем контракт вызовами cast 

Отправляем в терминал задачу:

  • Прочитать значение:

cast call $COUNTER «number()(uint256)» —rpc-url $RPC_URL

  • Установить значение и проверить:

cast send $COUNTER «setNumber(uint256)» 7 —rpc-url $RPC_URL —private-key $PRIVATE_KEY

cast call $COUNTER «number()(uint256)» —rpc-url $RPC_URL

Что должно получиться

  • До вызова setNumber функция number() возвращает 0.
  • После setNumber(7) — возвращает 7.

9. (Опционально) Форк сети и вызов в реальной среде

  1. Остановите все anvil командой: 

pkill anvil.

Проверьте RPC-URL провайдера (Alchemy/Infura/Ankr) — должен быть с реальным ключом; у Infura выключите «Require Project Secret».

Быстрая проверка:

export UPSTREAM_RPC=»https://eth-sepolia.g.alchemy.com/v2/ВАШ_КЛЮЧ»

curl -s -X POST -H «Content-Type: application/json» \

  —data ‘{«jsonrpc»:»2.0″,»method»:»eth_blockNumber»,»params»:[],»id»:1}’ \

  «$UPSTREAM_RPC»

  1.  Должен прийти JSON с result: «0x…».

Поднимите форк (на отдельном порту, чтобы не конфликтовать):

anvil —fork-url «$UPSTREAM_RPC» -p 8546

  1.  В логе увидите Forking … и Listening on 127.0.0.1:8546

Подключитесь к форку:

export RPC_URL=http://127.0.0.1:8546

cast block-number —rpc-url $RPC_URL

  1.  Большой номер блока → все ок.

Проверьте реальный токен:

Возьмите адрес из соответствующего блок-эксплорера сети и:

cast code 0xАДРЕС —rpc-url $RPC_URL

Например, 

cast code 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 (адрес USDC) —rpc-url https://mainnet.infura.io/v3/f39d740f03bb49c3b228b3f1c83df8f6 (ваш API)    

  1. cast call 0xАДРЕС «symbol()(string)» —rpc-url $RPC_URL

Что должно получиться

  • cast block-number возвращает большой номер блока (как в сети).
  • Вызов symbol() возвращает строку токена (например, USDC) — значит, форк работает.

Короткий чек-лист успеха

Проверяем проделанную работу. Если все пункты выполняются, базовая среда настроена, то можно идти дальше.

  • Установлен Foundry в Codespaces, команды печатают версии.
  • Проект создан, тесты проходят.
  • Контракт Counter собран без ошибок.
  • Локальная сеть anvil слушает 127.0.0.1:8545.
  • Контракт успешно задеплоен (Deployed to: 0x…).
  • Вызовы cast call/send работают (значение меняется).
  • (Опция) Форк создан: Forking… в логе и корректные ответы cast.
  • (Опция) Деплой в тестнет завершился, адрес виден в обозревателе.

Частые ошибки и быстрые фиксы

Ниже — самые распространенные проблемы при работе с Foundry и короткие решения. Найдите свой симптом — под ним указаны причина и точная команда, чтобы быстро починить.

  • bash: anvil: command not found
    Во вкладке #2 выполните:
    source ~/.bashrc && export PATH=»$HOME/.foundry/bin:$PATH» && anvil —version
    Если нужно — foundryup.
  • Address already in use (os error 98)
    Уже запущен другой anvil. Либо используйте его (RPC_URL=http://127.0.0.1:8545), либо остановите pkill anvil и запустите заново, либо запустите второй на другом порту:
    anvil -p 8546 и export RPC_URL=http://127.0.0.1:8546.
  • insufficient funds при деплое/отправке
    Возьмите другой PRIVATE_KEY из лога anvil — все они с тестовым балансом.
  • Ничего не печатает терминал
    Возможно, зажали вывод Ctrl+S. Нажмите Ctrl+Q.
    Либо вы в вкладке, где крутится anvil. Команды давайте во вкладке #1.
  • Переменные «пропали»
    Они действуют в текущем терминале. В новой вкладке — повторите export … или храните в .env и делайте source .env (и не коммитьте .env).

Заключение

Вы прошли путь от идеи до «живого» результата: собрали рабочую среду, написали и протестировали контракт, запустили локальную сеть, и (по желанию) опробовали форк. Это и есть практический минимум, который позволяет не гадать, а проверять — быстро, безопасно и воспроизводимо.

Смарт-контракты дают прозрачные правила и предсказуемость. Foundry добавляет к этому скорость и контроль: тесты (включая fuzz), инварианты, отчет по газу и деплой — все под рукой и без лишних зависимостей. Такой набор инструментов уже сегодня позволяет прототипировать токены/аукционы, воспроизводить баг-репорты и говорить с командой на языке фактов, а не предположений.

Чтобы прокачать навыки, попробуйте дальше:

  • Создать собственный стандартный токен. Реализуйте минимальный ERC-20/721, покройте тестами, сравните газ до/после оптимизаций.
  • Проверьте инварианты «как в проде»: задайте правила сохранения сумм/лимитов и запустите stateful-fuzz.
  • Вынесите шаги деплоя в script/ и запускайте через forge script —broadcast.
  • Опубликуйте исходники/метаданные (верификация) на обозревателе и проверьте, что в карточке контракта видны байткод и ABI.
  • Подключите forge test —gas-report в CI и снимайте «снапшоты» forge snapshot — так увидите, как меняется газ на каждом коммите.
  • Перенесите проект из Codespaces на локальную машину или разверните его в Sepolia, применяя отдельный тестовый ключ; файл .env не коммитьте (добавьте в .gitignore).

Не забывайте о безопасности:

  • Никогда не используйте «боевые» ключи в учебных сценариях и форках.
  • Не коммитьте секреты; храните их в .env.
  • Прежде чем выходить в основную сеть, убеждайтесь, что тесты зеленые, инварианты держатся, а газ укладывается в бюджет.

Если сохранить нынешнюю «песочницу», вы сможете быстро возвращаться к экспериментам: менять логику, добавлять проверки, смотреть газ — и за вечер превращать идеи в проверенный код.

Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *