Разработка смарт-контрактов на Ethereum с использованием Python и Vype

Разработка смарт-контрактов на Ethereum с использованием Python и Vype

Картинка к публикации: Разработка смарт-контрактов на Ethereum с использованием Python и Vype

Введение в язык Vyper

Основные особенности Vyper

Если вы когда-либо сталкивались с разработкой смарт-контрактов на блокчейне Ethereum, то наверняка знакомы с языком Solidity. Он стал де-факто стандартом для создания децентрализованных приложений (dApps). Однако, как известно из мира технологий, универсального решения не существует. Именно поэтому появился Vyper — язык программирования специально для написания смарт-контрактов, который делает акцент на простоте, безопасности и читаемости.

Vyper — это высокоуровневый язык программирования, предназначенный для работы с Ethereum Virtual Machine (EVM), подобно Solidity. Но в отличие от своего "старшего брата", он был создан с четким упором на устранение потенциальных уязвимостей и уменьшение сложности кода. Одной из его главных особенностей является минимализм: Vyper исключает многие элементы синтаксиса и конструкций языка, которые могут стать причиной ошибок или небезопасного поведения.

Одним из первых аспектов, которые выделяют Vyper среди других языков разработки смарт-контрактов, является его строгая типизация и отсутствие сложных конструкций вроде циклов while или модификаторов функций (modifiers). Это сделано намеренно: такие конструкции часто становятся источником логических ошибок или приводят к чрезмерному увеличению потребления при исполнении контракта. Разработчики Vyper считают: чем проще код — тем меньше вероятность возникновения ошибок.

Еще одной важной характеристикой Vyper является безопасность. Язык спроектирован так, чтобы минимизировать риски взлома через популярные атаки вроде переполнения целочисленных значений (integer overflow) или рекурсивных вызовов (reentrancy attacks). Например, все целые числа в Vyper автоматически проверяются на переполнение без необходимости дополнительного кодирования со стороны разработчика.

Что касается читаемости кода — здесь тоже есть свои плюсы. Благодаря ограниченному набору функций и более лаконичному синтаксису по сравнению с Solidity читать и понимать смарт-контракты на языке Vyper становится значительно проще даже для людей без глубоких знаний программирования. Это особенно важно в условиях открытого исходного кода: сторонние аудиторы могут быстрее находить ошибки и оценивать надежность вашего контракта.

Для сравнения с Solidity можно привести несколько ключевых моментов:

  • В Solidity больше возможностей для оптимизации производительности за счет использования сложных конструкций. В Vyper же предпочтение отдается безопасности.
  • Код на языке Solidity может быть менее предсказуемым из-за наличия таких инструментов как inline assembly. В Vyper использование низкоуровневого кода запрещено.
  • Читаемость кода в Solidity часто страдает из-за обилия синтаксических конструкций. В то время как подход "меньше значит лучше" делает код на языке Vyper доступнее широкому кругу специалистов.

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

Эти особенности делают язык идеальным выбором для тех случаев, когда требуется написать надежный контракт с минимальным количеством багов. Следующий шаг? Погрузиться глубже в структуру языка!

Установка и настройка окружения

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

Первое, что нужно знать: Vyper написан на Python. Это означает, что для его установки потребуется установленный интерпретатор Python версии 3.6 или выше (и давайте не будем пытаться использовать Python 2.x — он уже давно канул в лету). Кроме того, вам понадобятся такие инструменты как pip для управления зависимостями и удобная IDE (например, Visual Studio Code или PyCharm), чтобы писать код без боли.

Шаг 1: Установка Python

Если вы еще не установили Python:

  1. Перейдите на официальный сайт Python.
  2. Скачайте последнюю стабильную версию.
  3. При установке обязательно отметьте галочку "Add Python to PATH". Без этого настройка вызовет лишние проблемы.

Проверьте успешность установки командой:

python --version

или

python3 --version

Шаг 2: Установка виртуального окружения

Чтобы избежать конфликта зависимостей между проектами (а это случается чаще, чем хотелось бы), создадим изолированное виртуальное окружение:

python -m venv vyper_env
source vyper_env/bin/activate      # Для Linux/MacOS
vyper_env\Scripts\activate.bat     # Для Windows

После активации виртуального окружения терминал будет показывать префикс (vyper_env), указывая на то, что вы работаете в изоляции от системы.

Шаг 3: Установка Vyper через pip

Теперь пришло время установить сам компилятор Vyper:

pip install vyper

Проверьте корректность установки командой:

vyper --version

Если все прошло успешно, вы увидите текущую версию установленного компилятора.

Шаг 4: Настройка среды разработки (IDE)

Для комфортной работы с кодом важно правильно настроить вашу среду разработки. Рассмотрим пример с Visual Studio Code:

  1. Скачайте и установите Visual Studio Code.
  2. Установите расширение "Solidity" для поддержки синтаксиса .vy файлов (да-да, оно подходит и для Vyper).
  3. Настройте интеграцию терминала VS Code с вашим виртуальным окружением (vyper_env) через настройки проекта.

Дополнительно можно подключить линтеры вроде Flake8 или интеграцию с MyPy для проверки типов — они помогут избегать ошибок еще до запуска кода.

Шаг 5: Проверка компиляции простого контракта

Создайте файл HelloWorld.vy со следующим содержанием:

# Простой контракт-пример на языке Vyper

@external
def hello() -> String[20]:
    return "Hello, world!"

Скомпилируйте его через терминал следующей командой:

vyper HelloWorld.vy  

Если установка прошла успешно — вместо ошибок вы получите байт-код вашего контракта!

На этом базовая настройка завершена! Теперь ваше рабочее пространство готово для написания смарт-контрактов любой сложности. В дальнейшем мы рассмотрим создание более сложных контрактов и их тестирование в реальной блокчейн-среде — так что не переключайтесь!

Основы смарт-контрактов на Vyper

Синтаксис и структура кода

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

Начнем с самой базовой концепции: структуры контракта. Смарт-контракт в Vyper представляет собой файл с расширением .vy, содержащий определения переменных состояния, функций и событий. Вот простой пример:

# Пример минимального смарт-контракта на Vyper

# Определение переменной состояния
greeting: public(String[20])

@deploy
def __init__():
    # Инициализация переменной при развертывании контракта
    self.greeting = "Hello, world!"

@external
def set_greeting(new_greeting: String[20]):
    # Функция для изменения значения переменной состояния
    self.greeting = new_greeting

@view 
@external
def get_greeting() -> String[20]:
    # Читаем текущее значение greeting без изменения состояния блокчейна (view)
    return self.greeting

Переменные состояния определяются вне функций и хранятся непосредственно в блокчейне. В приведенном выше примере greeting — это строковая переменная ограниченного размера (максимум 20 символов). Ключевое слово public автоматически создает геттер-функцию для этой переменной.

Типы данных в Vyper строго типизированы, например:

  • uint256: неотрицательное целое число;
  • int128: знаковое целое число (от -2^127 до 2^127 - 1);
  • address: адрес кошелька или контракта;
  • String[N]: строка фиксированной длины N.

Строгая типизация предотвращает многие ошибки еще на этапе компиляции.

Функции — основной механизм взаимодействия со смарт-контрактом. Каждая функция должна быть помечена декоратором:

  • @external используется для вызова функции извне.
  • @internal указывает, что функцию можно вызывать только изнутри контракта.
  • @view означает отсутствие изменений состояния блокчейна.
  • @pure гарантирует отсутствие доступа как к состоянию контракта, так и к глобальным данным блокчейна (например, времени).

Функция конструктора (__init__) помечается декоратором @deploy и вызывается единожды при развертывании контракта.

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

# Определение переменной состояния
greeting: public(String[20])

# Событие для отслеживания изменений приветствия
event GreetingChanged:
    old_greeting: String[20]
    new_greeting: String[20]

@deploy
def __init__():
    # Инициализация переменной при развертывании контракта
    self.greeting = "Hello, world!"

@external
def set_greeting(new_greeting: String[20]):
    # Записываем событие в логах транзакций
    log GreetingChanged(self.greeting, new_greeting)
    # Функция для изменения значения переменной состояния
    self.greeting = new_greeting

@view
@external
def get_greeting() -> String[20]:
    # Читаем текущее значение greeting без изменения состояния блокчейна (view)
    return self.greeting

В этом примере каждое изменение приветствия записывается в журнал событий с помощью функции log.

Несколько важных моментов относительно синтаксиса:

  • Отступы имеют решающее значение! Как и в Python, язык Vyper использует отступы вместо фигурных скобок.
  • Отсутствие циклов типа while. Вместо этого рекомендуется использовать строгие условия или заранее известные итерационные пределы (for in range()).
  • Исключение низкоуровневого функционала. В Vyper отсутствует поддержка inline assembly, что повышает безопасность.

Vyper заставляет вас продумывать каждый элемент кода благодаря своей лаконичности и строгим конструкциям. Это особенно важно при работе с деньгами пользователей в децентрализованных приложениях.

Создание первого смарт-контракта

Создание первого смарт-контракта на языке Vyper — это как освоение велосипеда: сначала немного страшно, но как только вы начнете крутить педали (то есть писать код), все станет понятнее. Давайте разберем процесс создания простого контракта, который поможет вам понять основные компоненты и принципы работы с этим языком.

В качестве первого реального примера давайте создадим контракт, который будет выступать в роли «простой копилки». Этот контракт позволит вносить средства на свой адрес и выводить их при необходимости. Такой функционал уже ближе к реальным финансовым операциям, чем простое хранение строки.

Вот наш первый смарт-контракт:

# SimplePiggyBank.vy

# Объявление переменной состояния для хранения владельца
owner: public(address)

# Событие для отслеживания вкладов
event Deposit:
    sender: address
    amount: uint256

# Событие для отслеживания выводов
event Withdrawal:
    recipient: address
    amount: uint256

@deploy
def __init__():
    """
    Функция-конструктор (вызов при развёртывании).
    Устанавливает владельца контракта.
    """
    self.owner = msg.sender

@external
@payable
def deposit():
    """
    Функция для внесения средств в копилку.
    Так как она помечена @payable, отправитель может
    прикрепить к транзакции эфир.
    """
    # Записываем событие о внесении депозита
    log Deposit(msg.sender, msg.value)

@external
def withdraw(amount: uint256):
    """
    Функция для вывода средств. Только владелец контракта
    может выводить эфир из копилки.
    
    Аргументы:
      - amount: Сумма в wei, которую нужно вывести.
    """
    # Проверяем права доступа
    assert msg.sender == self.owner, "You are not the owner!"
    # Проверяем, что в копилке достаточно средств
    assert self.balance >= amount, "Insufficient contract balance!"

    # Выполняем перевод
    send(self.owner, amount)

    # Записываем событие о выводе
    log Withdrawal(self.owner, amount)

@view
@external
def get_balance() -> uint256:
    """
    Возвращает текущий баланс контракта.
    Так как функция помечена @view, она не изменяет
    состояние блокчейна.
    """
    return self.balance

Ключевые отличия от «контракта-приветствия»:

  1. Работа с эфиром
    • Использование декоратора @payable в функции deposit позволяет контракту принимать эфир.
    • Для вывода средств используется встроенная функция send.
  2. Ограничение доступа
    • Применена конструкция assert msg.sender == self.owner, чтобы только владелец мог выводить средства.
  3. Использование встроенной переменной self.balance
    • В Vyper текущий баланс контракта доступен через self.balance.
    • При выводе мы можем сравнивать запрашиваемую сумму с актуальным балансом.
  4. События
    • Добавлены события Deposit и Withdrawal для логирования действий — это позволит наблюдать за транзакциями без постоянных вызовов get_balance.

Теперь у вас есть базовый шаблон! На следующем этапе можно добавить события для отслеживания изменений или даже попробовать реализовать базовый токен стандарта ERC20... Но об этом позже!

Взаимодействие со смарт-контрактами

Компиляция и тестирование смарт-контрактов

Когда ваш смарт-контракт на Vyper написан, наступает время проверить его работоспособность. Этап компиляции и тестирования — это не просто проверка синтаксиса, но и способ убедиться, что контракт выполняет свою задачу корректно. Давайте разберем процесс пошагово.

Компиляция смарт-контракта

Первый шаг после написания контракта — это его компиляция. В случае с Vyper это означает преобразование вашего читаемого кода в байт-код, который будет исполняться Ethereum Virtual Machine (EVM). Для этого используется встроенный инструмент vyper, установленный ранее.

Предположим, у вас есть файл SimplePiggyBank.vy:

vyper SimplePiggyBank.vy

Если ошибок нет, результатом станет байт-код контракта, выведенный в консоль. Однако, для дальнейшего использования обычно сохраняют этот код в файл. Для этого можно использовать флаг -o:

vyper -f abi,bytecode -o ./compiled/SimplePiggyBank.json SimplePiggyBank.vy

Эта команда сохранит скомпилированный контракт в папке build в формате JSON, содержащем как байт-код, так и ABI (Application Binary Interface).

Дополнительная проверка ABI:

ABI (Application Binary Interface) необходим для взаимодействия с контрактом через приложения, такие как dApps, или с помощью инструментов (например, веб-интерфейсов). Чтобы получить ABI вашего контракта отдельно:

vyper -f abi -o ./compiled/SimplePiggyBank_abi.json SimplePiggyBank.vy

Если нужен только байт-код:

vyper -f bytecode -o ./compiled/SimplePiggyBank_bytecode.json SimplePiggyBank.vy

Теперь у вас есть все необходимые данные: байт-код и ABI.

Простая компиляция недостаточна, важно удостовериться, что ваш контракт работает как задумано. Существует несколько популярных инструментов для тестирования Vyper-контрактов: Brownie и Remix.

Brownie: Python-подход к тестированию

Brownie — это фреймворк на Python для разработки и тестирования смарт-контрактов. Он идеально подходит для работы с Vyper благодаря интеграции с Python. Brownie предоставляет мощные инструменты для локальной разработки, тестирования и деплоя контрактов.

Установка Brownie

Убедитесь, что вы активировали виртуальное окружение (venv), затем установите Brownie:

pip install eth-brownie

Подготовка проекта

Предположим, у вас есть проект со следующей структурой:

my_vyper_project/
  ├─ contracts/
  │   └─ SimplePiggyBank.vy
  ├─ tests/
  │   └─ test_SimplePiggyBank.py
  ├─ scripts/
  │   └─ deploy.py
  ├─ compiled/
  │   └─ ...
  └─ brownie-config.yaml

brownie-config.yaml ранее не описывался, но для дальнейшего рассмотрения пригодится.

networks:
  default: development
  development:
    host: http://127.0.0.1
    port: 8545
    cmd: ganache --server.port=8545 --chain.chainId=1337 --miner.blockTime=1 --logging.debug
    timeout: 120

    gas_price: 10 gwei
    gas_limit: auto

Создайте новый проект инициализируйте Brownie:

brownie init --force

Компиляция контракта

Укажите версию компилятора в начале файла с контрактом, например:

# @version ^0.4.0

проверить её можно командой 

vyper --version
в ответе будет что-то вроде:
0.4.0+commit.e9db8d9

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

brownie compile

Локальная разработка и развертывание

Для локального тестирования используйте Ganache — локальный блокчейн-симулятор. Установите его, если он ещё не установлен:

npm install -g ganache

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

ganache

Теперь напишите скрипт для развертывания контракта в файле scripts/deploy.py:

from brownie import accounts, SimplePiggyBank


def main():
    # Берём первый аккаунт из локальной сети
    deployer = accounts[0]

    # Развёртываем контракт
    piggy_bank = SimplePiggyBank.deploy({"from": deployer})

    print(f"Contract deployed at: {piggy_bank.address}")

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

brownie run scripts/deploy.py --network development

Написание тестов

Brownie использует pytest для написания тестов. Создайте файл tests/test_piggy_bank.py и добавьте следующие тесты:

import pytest


def test_initial_owner(accounts, SimplePiggyBank):
    piggy_bank = SimplePiggyBank.deploy({'from': accounts[0]})
    assert piggy_bank.owner() == accounts[0]


def test_deposit_and_withdraw(accounts, SimplePiggyBank):
    piggy_bank = SimplePiggyBank.deploy({'from': accounts[0]})

    # Делаем депозит 1 ether
    deposit_tx = piggy_bank.deposit({'from': accounts[1], 'value': "1 ether"})
    deposit_tx.wait(1)

    assert piggy_bank.get_balance() == "1 ether"

    # Попытка вывести средства не владельцем
    with pytest.raises(Exception):
        piggy_bank.withdraw("1 ether", {'from': accounts[1]})

    # Вывод средств владельцем
    withdraw_tx = piggy_bank.withdraw("1 ether", {'from': accounts[0]})
    withdraw_tx.wait(1)

    assert piggy_bank.get_balance() == 0

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

brownie test
brownie test

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

  1. Работа с локальной сетью:
    • Ganache запускает симуляцию сети Ethereum. Вы можете добавлять тестовые аккаунты, совершать транзакции и проверять баланс.
    • Brownie автоматически подключается к Ganache, если локальная сеть активна.
  2. Без регистрации в Web3:
    • Локальная сеть полностью автономна. Вам не нужно регистрироваться на Infura или других сервисах для разработки и тестирования.
  3. Простота деплоя:
    • Все операции производятся локально, что упрощает отладку и экспериментирование.

Используя Brownie и Ganache, вы можете разрабатывать, тестировать и отлаживать смарт-контракты полностью локально, без необходимости регистрироваться в Web3-сервисах. Это удобный и быстрый способ освоить разработку смарт-контрактов и убедиться в их корректной работе до публикации в реальной сети.

Деплой смарт-контрактов в сеть Ethereum

Развертывание смарт-контракта в сети Ethereum — это следующий шаг после написания и тестирования кода. Мы будем использовать библиотеку web3.py, которая предоставляет удобный интерфейс для взаимодействия с блокчейном Ethereum.

Подготовка окружения

Для начала убедитесь, что у вас установлены все необходимые инструменты:

  1. Создайте виртуальное окружение (если еще не сделали этого) и активируйте его.
  2. Установите web3.py:

    pip install web3
  3. Подготовьте скомпилированный контракт: байт-код (bytecode) и ABI, которые были получены на этапе компиляции через vyper.

Также потребуется доступ к Ethereum-узлу:

Структура деплоя

1. Импортируем зависимости

Создайте файл deploy_contract.py и начните с импорта необходимых модулей:

from web3 import Web3
import json

2. Настройка соединения с узлом

Укажите URL вашего Ethereum-узла:

  • Если вы используете Ganache:

    w3 = Web3(Web3.HTTPProvider("http://127.0.0.1:8545"))
  • Для подключения к Infura замените <YOUR_INFURA_PROJECT_ID> своим проектным ID из Infura:

    w3 = Web3(Web3.HTTPProvider("https://ropsten.infura.io/v3/<YOUR_INFURA_PROJECT_ID>"))

Проверьте подключение командой:

if w3.is_connected():
    print("Подключение успешно!")
else:
    raise Exception("Не удалось подключиться к узлу.")

3. Настройка аккаунта для транзакций

Для развертывания контракта вам понадобится аккаунт со средствами (ETH). В случае использования Ganache это будет один из автоматически созданных ключей. Из лога Ganache (при запуске) вы можете увидеть список тестовых аккаунтов и приватных ключей. Допустим, возьмём самый первый:

private_key = "ВАШ_ПРИВАТНЫЙ_КЛЮЧ"
deployer_address = "ВАШ_ETHEREUM_АДРЕС"

# Проверяем баланс аккаунта (опционально)
balance = w3.eth.get_balance(deployer_address)
print(f"Баланс: {w3.from_wei(balance, 'ether')} ETH")

4. Загрузка ABI и байт-кода контракта

Загрузите предварительно созданные файлы ABI и байт-кода:

with open('compiled/SimplePiggyBank_abi.json', 'r') as abi_file:
    contract_abi = json.load(abi_file)

with open('compiled/SimplePiggyBank_byte.json', 'r') as bytecode_file:
    contract_bytecode = bytecode_file.read().strip()

Теперь у вас есть всё необходимое для создания экземпляра контракта.

Деплой смарт-контракта

Развернем контракт в сети следующим образом:

1. Создаем объект контракта на основе ABI:

Contract = w3.eth.contract(abi=contract_abi, bytecode=contract_bytecode)

2. Генерируем транзакцию для деплоя:

using_ganache = True
transaction = Contract.constructor().build_transaction({
    "chainId": 1337 if using_ganache else 3,  # Chain ID: Ganache (1337), Ropsten (03)
    "gas": 3000000,
    "gasPrice": w3.to_wei('10', 'gwei'),  # классический gasPrice для локальной сети
    "nonce": w3.eth.get_transaction_count(deployer_address),
})

Обратите внимание: всегда проверяйте актуальный Chain ID вашей сети!

Подписание транзакции

1. Транзакция должна быть подписана вашим приватным ключом перед отправкой в сеть:

signed_txn = w3.eth.account.sign_transaction(transaction, private_key)
txn_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction)

print(f"Транзакция отправлена! Хэш транзакции: {txn_hash.hex()}")

2. После этого следует дождаться подтверждения выполнения транзакции:

txn_receipt = w3.eth.wait_for_transaction_receipt(txn_hash)
print(f"Контракт развернут по адресу: {txn_receipt.contractAddress}")

Адрес нового контракта вы можете использовать для дальнейшего взаимодействия через функции API или dApps.

transaction

Теперь ваш контракт успешно загружен в блокчейн! Вы только что прошли весь путь от написания простого Vyper-смарт-контракта до его развертывания в блокчейн-сети.

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

Расширенные возможности Vyper

Работа с токенами ERC-20 и ERC-721

Когда речь заходит о создании токенов в блокчейне Ethereum, стандарты ERC-20 и ERC-721 являются основой большинства приложений. Они обеспечивают совместимость между смарт-контрактами и позволяют легко интегрировать токены в экосистему кошельков, бирж и других dApps. Vyper отлично подходит для реализации этих стандартов благодаря своей простоте, строгой типизации и акценту на безопасности. Давайте разберемся, как создавать такие токены с помощью языка Vyper.

Создание ERC-20 токена

Стандарт ERC-20 описывает интерфейс для взаимозаменяемых токенов (fungible tokens), таких как криптовалюты или utility-токены. Он определяет минимальный набор функций: transfer, approve, transferFrom и т.д., а также события вроде Transfer и Approval.

Вот базовая реализация контракта ERC-20 на языке Vyper:

interfaces/ERC20.vyi

# @version ^0.4.0

interface ERC20:
    def totalSupply() -> uint256: view
    def balanceOf(owner: address) -> uint256: view
    def allowance(owner: address, spender: address) -> uint256: view
    def transfer(to: address, amount: uint256) -> bool: nonpayable
    def approve(spender: address, amount: uint256) -> bool: nonpayable
    def transferFrom(from_: address, to: address, amount: uint256) -> bool: nonpayable

contracts/ERC20.vy

# Пример контракта ERC-20 на языке Vyper
# @version ^0.4.0

from interfaces import ERC20

implements: ERC20

name: public(String[32])
symbol: public(String[10])
decimals: public(uint256)
total_supply: public(uint256)

balances: HashMap[address, uint256]
allowances: HashMap[address, HashMap[address, uint256]]

@deploy
def __init__(initial_supply: uint256):
    self.name = "MyToken"
    self.symbol = "MTK"
    self.decimals = 18
    self.total_supply = initial_supply * 10**self.decimals
    self.balances[msg.sender] = self.total_supply

@view
@external
def balanceOf(account: address) -> uint256:
    return self.balances[account]

@external
def transfer(recipient: address, amount: uint256) -> bool:
    assert self.balances[msg.sender] >= amount, "Insufficient funds"
    self.balances[msg.sender] -= amount
    self.balances[recipient] += amount
    log Transfer(msg.sender, recipient, amount)
    return True

@external
def approve(spender: address, amount: uint256) -> bool:
    self.allowances[msg.sender][spender] = amount
    log Approval(msg.sender, spender, amount)
    return True

@view
@external
def allowance(owner_: address, spender_: address) -> uint256:
    return self.allowances[owner_][spender_]

@external
def transferFrom(sender: address, recipient: address, amount: uint256) -> bool:
    allowed_amount: uint256 = self.allowances[sender][msg.sender]
    assert allowed_amount >= amount and self.balances[sender] >= amount, "Insufficient funds or exceeded authorization"
    self.allowances[sender][msg.sender] -= amount
    self.balances[sender] -= amount
    self.balances[recipient] += amount
    log Transfer(sender, recipient, amount)
    return True

event Transfer:
    sender_indexed: indexed(address)
    receiver_indexed: indexed(address)
    value: uint256

event Approval:
    owner_indexed: indexed(address)
    spender_indexed: indexed(address)
    value: uint256

Создание ERC-721 (NFT)

ERC-721 предназначен для создания невзаимозаменяемых токенов (non-fungible tokens). Такие токены часто применяются в играх (например коллекционные предметы), искусстве (NFT-художественные произведения) или даже недвижимости.

Базовый пример реализации NFT-контракта:

interfaces/ERC721.vyi

# @version ^0.4.0

interface ERC721:
    def balanceOf(owner: address) -> uint256: view
    def ownerOf(tokenId: uint256) -> address: view
    def safeTransferFrom(from_: address, to: address, tokenId: uint256): nonpayable
    def transferFrom(from_: address, to: address, tokenId: uint256): nonpayable
    def approve(to: address, tokenId: uint256): nonpayable
    def setApprovalForAll(operator: address, approved: bool): nonpayable
    def getApproved(tokenId: uint256) -> address: view
    def isApprovedForAll(owner: address, operator: address) -> bool: view

contracts/ERC721.vy

# Пример контракта ERC-721 на языке Vyper
# @version ^0.4.0

from interfaces import ERC721

implements: ERC721

NULL_ADDRESS: constant(address) = 0x0000000000000000000000000000000000000000

name: public(String[32])
symbol: public(String[10])

# Маппинг от идентификатора токена к владельцу
owners: HashMap[uint256, address]
# Маппинг от владельца к количеству принадлежащих ему токенов
balances: HashMap[address, uint256]
# Маппинг от идентификатора токена к адресу, имеющему право его передать
token_approvals: HashMap[uint256, address]
# Маппинг для глобальных разрешений на управление токенами
operator_approvals: HashMap[address, HashMap[address, bool]]

event Transfer:
    sender_indexed: indexed(address)
    receiver_indexed: indexed(address)
    tokenId: uint256

event Approval:
    owner_indexed: indexed(address)
    approved_indexed: indexed(address)
    tokenId: uint256

event ApprovalForAll:
    owner_indexed: indexed(address)
    operator_indexed: indexed(address)
    approved: bool

@deploy
def __init__():
    self.name = "MyNFT"
    self.symbol = "MNFT"

@view
@external
def balanceOf(owner: address) -> uint256:
    assert owner != NULL_ADDRESS, "The address cannot be null"
    return self.balances[owner]

@view
@external
def ownerOf(tokenId: uint256) -> address:
    owner: address = self.owners[tokenId]
    assert owner != NULL_ADDRESS, "The token doesn't exist"
    return owner

@external
def approve(to: address, tokenId: uint256):
    owner: address = self.owners[tokenId]
    assert to != owner, "You can't approve of yourself"
    assert msg.sender == owner or self.operator_approvals[owner][msg.sender], "No permit"

    self.token_approvals[tokenId] = to
    log Approval(owner, to, tokenId)

@view
@external
def getApproved(tokenId: uint256) -> address:
    assert self.owners[tokenId] != NULL_ADDRESS, "The token doesn't exist"
    return self.token_approvals[tokenId]

@external
def setApprovalForAll(operator: address, approved: bool):
    assert operator != msg.sender, "You cannot set a permission for yourself"
    self.operator_approvals[msg.sender][operator] = approved
    log ApprovalForAll(msg.sender, operator, approved)

@view
@external
def isApprovedForAll(owner: address, operator: address) -> bool:
    return self.operator_approvals[owner][operator]

@external
def transferFrom(from_: address, to: address, tokenId: uint256):
    assert to != NULL_ADDRESS, "The recipient cannot be a null address"
    owner: address = self.owners[tokenId]
    assert owner == from_, "The sender is not the owner"
    assert msg.sender == owner or self.token_approvals[tokenId] == msg.sender or self.operator_approvals[owner][msg.sender], "No authorization for transmission"

    # Сброс разрешений
    self.token_approvals[tokenId] = NULL_ADDRESS

    # Обновление балансов
    self.balances[from_] -= 1
    self.balances[to] += 1

    # Изменение владельца токена
    self.owners[tokenId] = to

    log Transfer(from_, to, tokenId)

@external
def mint(to: address, tokenId: uint256):
    assert to != NULL_ADDRESS, "The address cannot be null"
    assert self.owners[tokenId] == NULL_ADDRESS, "The token already exists"

    self.balances[to] += 1
    self.owners[tokenId] = to

    log Transfer(NULL_ADDRESS, to, tokenId)

@external
def burn(tokenId: uint256):
    owner: address = self.owners[tokenId]
    assert owner == msg.sender, "Only the owner can burn the token"

    # Обновление балансов и удаление владельца
    self.balances[owner] -= 1
    self.owners[tokenId] = NULL_ADDRESS

    log Transfer(owner, NULL_ADDRESS, tokenId)

Особенности реализации в Vyper:

  1. Простота и безопасность:
    • Контракт минимизирует количество функций, соответствуя стандарту ERC-721.
    • Строгая типизация предотвращает ошибки переполнения и неправильного использования данных.
  2. События (log):
    • События Transfer, Approval и ApprovalForAll позволяют отслеживать передачу токенов и изменения разрешений.
  3. Механизм разрешений:
    • Поддерживаются индивидуальные (approve) и глобальные (setApprovalForAll) разрешения для управления токенами.
  4. Функция mint:
    • Позволяет создавать новые токены, назначая их владельцу.
    • Проверяет уникальность идентификатора токена.
  5. Функция burn:
    • Позволяет владельцу уничтожить токен.
  6. Совместимость:
    • Реализация полностью совместима с интерфейсом ERC-721, что позволяет легко интегрировать токены в существующую экосистему.

Оптимизация и безопасность смарт-контрактов

Расширенные возможности языка Vyper делают его идеальным инструментом для написания безопасных и оптимизированных смарт-контрактов. Его минималистичный дизайн не просто эстетичен, но также исключает целый спектр потенциальных уязвимостей, характерных для более сложных языков вроде Solidity. Однако даже на таком строгом языке важно знать подходы к написанию качественного кода, чтобы повысить надежность и эффективность ваших контрактов.

Принципы безопасности в Vyper:

1. Избегайте переполнения чисел

Одна из самых распространенных атак на смарт-контракты — это переполнение или недостаток (overflow/underflow) целых чисел. В отличие от Solidity до версии 0.8.x, где разработчику нужно было вручную использовать библиотеки типа SafeMath, в Vyper такие ошибки невозможны: язык автоматически проверяет границы значений.

@external
def safe_addition(a: uint256, b: uint256) -> uint256:
    return a + b  # Автоматическая проверка на переполнение

Попытка выйти за пределы допустимых значений вызовет ошибку компиляции или выполнения без необходимости дополнительных библиотек.

2. Контроль доступа

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

owner: public(address)

@external
def __init__():
    self.owner = msg.sender

@external
def restricted_function():
    assert msg.sender == self.owner, "Access denied"

Такая простая конструкция помогает избежать проблем с несанкционированным доступом.

3. Предотвращение reentrancy-атак

Reentrancy-атаки — это одна из самых известных угроз для Ethereum-смарт-контрактов (да здравствует DAO-хак). В Vyper возможность таких атак снижена благодаря отсутствию модификаторов функций (modifiers) и низкоуровневых вызовов (call), которые могут быть использованы злоумышленниками.

Оптимизация смарт-контрактов в Vyper:

Vyper делает акцент на читаемости и минимализме, что само по себе способствует оптимизации. Но есть несколько дополнительных рекомендаций:

1. Используйте фиксированные размеры данных

Память блокчейна дорога (буквально), поэтому использование фиксированных типов данных может существенно снизить расходы газа:

  • Вместо String используйте bytes[N], если длина строки известна заранее.
  • Для массивов старайтесь задавать максимальную длину при объявлении:
data: bytes[32]
numbers: uint256[10]

Это упрощает работу компилятора и снижает стоимость операций.

2. Минимизируйте циклы

Хотя циклы типа for поддерживаются в Vyper с жесткими ограничениями по итерациям (нельзя писать бесконечные циклы), они могут быстро привести к увеличению стоимости транзакции при большом числе элементов.

Лучше избегать длинных списков внутри контракта; вместо этого храните данные вне сети или разбивайте задачи между несколькими транзакциями.

3. События вместо хранения данных

Для временной информации используйте события (event) вместо переменных состояния — так вы сохраните газ:

event DataStored:
    user_indexed: indexed(address)
    value: uint256

@external 
def log_data(value: uint256):
    log DataStored(msg.sender, value)

События дешевле записи в хранилище блокчейна и полезны для отслеживания истории действий без увеличения размера контракта.

Дополнительные советы

  1. Всегда тестируйте ваши контракты через фреймворки вроде Brownie или Pytest.
  2. Делайте аудит своего кода перед развертыванием даже в тестовой сети.
  3. Документируйте каждую функцию! Ясная структура комментариев облегчит понимание вашего проекта сторонними разработчиками или аудиторами.
  4. Будьте внимательны с магическими числами ("magic numbers"). Лучше определяйте их как константы:

    MAX_SUPPLY: constant(uint256) = 1000000

Заключение? Код на языке Vyper должен быть максимально простым — именно так достигается безопасность и надежность вашей dApp! Следуя этим принципам, вы сможете создать эффективный контракт без лишних рисков для пользователей (и своих нервов).

Практическое применение и перспективы

Реальные примеры использования

Практическое применение Vyper в реальных проектах демонстрирует его потенциал как отличного инструмента для создания безопасных и эффективных смарт-контрактов. Несмотря на то, что Solidity остается более популярным выбором среди разработчиков Ethereum, Vyper активно используется для реализации проектов, где приоритет отдается безопасности, читаемости и минимизации атак поверхности.

Одним из ярких примеров является использование Vyper в децентрализованных финансовых приложениях (DeFi). Например, такие проекты как Curve Finance выбрали этот язык благодаря его фокусу на безопасности. Curve — это протокол автоматизированного маркет-мейкера (AMM), который оптимизирует обмен стабильными активами с минимальными комиссиями. Разработчики Curve утверждают, что строгая типизация Vyper и упрощенный синтаксис помогли избежать множества потенциальных уязвимостей во время разработки контракта. Использование языка позволило проекту установить новые стандарты надежности в DeFi-секторе.

Еще один пример успешного применения — Synthetix, платформа для торговли деривативами на основе блокчейна Ethereum. Некоторые ключевые компоненты системы Synthetix были переписаны с использованием Vyper именно из-за необходимости обеспечить максимальную прозрачность логики контрактов и предотвратить ошибки из-за сложной структуры кода.

В сфере NFT (невзаимозаменяемых токенов) также можно найти проекты, использующие возможности Vyper. Например, небольшие коллекционные платформы применяют этот язык для создания смарт-контрактов с простой логикой выпуска токенов и управления правами владельцев. Это особенно актуально в условиях роста индустрии NFT: простота аудита контрактов становится критически важной для привлечения доверия пользователей.

На институциональном уровне Vyper часто выбирают для реализации DAO (децентрализованных автономных организаций). Строгий контроль доступа к функциям и ограничение низкоуровневой логики делают его идеальным кандидатом для написания контрактов голосования или распределения средств внутри DAO.

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

Будущее Vyper и экосистемы Ethereum

Vyper уверенно закрепился как язык программирования, ориентированный на безопасность и минимализм в экосистеме Ethereum. Его простота и строгая типизация делают его привлекательным выбором для проектов, где ошибка может стоить миллионы долларов или подорвать доверие пользователей. В долгосрочной перспективе Vyper имеет все шансы стать ключевым инструментом для разработки высоконадежных смарт-контрактов, особенно в таких критически важных сферах, как децентрализованные финансы (DeFi), управление DAO и токенизация активов.

Несмотря на текущую доминацию Solidity, растущий акцент на безопасности контрактов стимулирует разработчиков исследовать альтернативы. Vyper уже стал популярным среди команд с высоким уровнем ответственности — например, Curve Finance доказал жизнеспособность языка в условиях реального мира. Этот прецедент показывает: проекты с миллиардными объемами ликвидности готовы выбирать менее "популярные" инструменты ради надежности.

Перспективы Vyper усиливаются благодаря развитию самого Ethereum. С переходом сети на PoS (Proof of Stake) и внедрением таких обновлений, как sharding и rollups, потребность в безопасных контрактах только возрастет. Более того, снижение издержек газа после внедрения EIP-1559 открывает новые возможности для использования оптимизированного кода Vyper даже при более сложной логике.

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

Однако вызовы остаются: экосистема разработки вокруг языка пока не так развита по сравнению с Solidity. Увеличение числа библиотек и инструментов тестирования станет важным шагом вперед для привлечения большего числа разработчиков.

В будущем роль Vyper может расшириться за пределы традиционных dApps:

  1. Государственные приложения: системы голосования или распределения средств.
  2. Корпоративные сети: управление цепочками поставок или автоматизация финансовых операций.
  3. NFT 2.0: создание новых стандартов невзаимозаменяемых токенов с повышенными требованиями к безопасности прав владения.

И хотя путь развития будет нелегким из-за конкуренции со стороны других языков (например, Rust через Substrate в Polkadot), ориентация Ethereum на универсальность обеспечивает прочный фундамент для дальнейшего роста Vyper.

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


Читайте также:

ChatGPT
Eva
💫 Eva assistant

Выберите способ входа