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

TIP-3 в сетях Everscale и Venom

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

Суть реализации заключается в том, что в сети существует смарт-контракт TokenRoot, который:

  • Хранит данные о токене:

    • Название токена;

    • Тикер токена;

    • Размер дробной части;

    • Адресе смарт-контракта-владельца токена;

    • Эмиссия токена;

  • Позволяет владельцу контракта выпускать новые токены;

  • Позволяет сжигать токены.

  • Позволяет создавать смарт-контракты кошельки и начислять на них токены посредством минта.

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

deployWallet

Метод deployWallet смарт-котнракта TokenRoot может быть вызван кем угодно:

function deployWallet(
     address walletOwner,
     uint128 deployWalletValue
) public override responsiblereturns (address tokenWallet) {
     require(
      walletOwner.value != 0,
      TokenErrors.WRONG_WALLET_OWNER
     );

     tvm.rawReserve(_reserve(), 0);

     tokenWallet = _deployWallet(
       _buildWalletInitData(walletOwner),
       deployWalletValue,
       msg.sender
     );

     return { 
        value: 0,
        flag: TokenMsgFlag.ALL_NOT_RESERVED,
        bounce: false 
     } tokenWallet;
    }

В результате, в сети появится новый смарт-контракт, на балансе которого можно хранить токены контракта TokenRoot.

Отправка TIP-3 токенов

Сам смарт-контракт Wallet обладает всеми необходимыми методами для управления балансами, нас интересуют transfer и acceptTransfer:

function transfer(
        uint128 amount,
        address recipient,
        uint128 deployWalletValue,
        address remainingGasTo,
        bool notify,
        TvmCell payload
    )
        override
        external
        onlyOwner
    {
        require(amount > 0, TokenErrors.WRONG_AMOUNT);
        require(amount <= balance_, TokenErrors.NOT_ENOUGH_BALANCE);
        require(recipient.value != 0 && recipient != owner_, TokenErrors.WRONG_RECIPIENT);

        tvm.rawReserve(_reserve(), 0);
        
        // получение кода смарт-контракта получателя
        TvmCell stateInit = _buildWalletInitData(recipient);

        address recipientWallet;

        if (deployWalletValue > 0) {
            recipientWallet = _deployWallet(stateInit, deployWalletValue, remainingGasTo);
        } else {
            recipientWallet = address(tvm.hash(stateInit));
        }

        balance_ -= amount;


        // Вызов метода acceptTransfer у контракта-получателя
        ITokenWallet(recipientWallet).acceptTransfer{ value: 0, flag: TokenMsgFlag.ALL_NOT_RESERVED, bounce: true }(
            amount,
            owner_,
            remainingGasTo,
            notify,
            payload
        );
    }

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

Смарт-контракты при выставленном флаге onBounce: true отправляют сообщения об ошибке исполнения транзакции, инициированной полученным сообщением. В Everscale и Venom такие обратные сообщения называются bounce messages. По сути, любое сообщение может быть отправлено с флагом onBounce: true, сообщения об отправке средств или развертывании контракта-кошелька в сети не являются исключением:

onBounce(TvmSlice body) external {
        tvm.rawReserve(_reserve(), 2);

        uint32 functionId = body.decode(uint32);

        if (functionId == tvm.functionId(ITokenWallet.acceptTransfer)) {
            uint128 amount = body.decode(uint128);
            balance_ += amount;
            IBounceTokensTransferCallback(owner_).onBounceTokensTransfer{
                value: 0,
                flag: TokenMsgFlag.ALL_NOT_RESERVED + TokenMsgFlag.IGNORE_ERRORS,
                bounce: false
            }(
                root_,
                amount,
                msg.sender
            );
        } else if (functionId == tvm.functionId(ITokenRoot.acceptBurn)) {
            uint128 amount = body.decode(uint128);
            balance_ += amount;
            IBounceTokensBurnCallback(owner_).onBounceTokensBurn{
                value: 0,
                flag: TokenMsgFlag.ALL_NOT_RESERVED + TokenMsgFlag.IGNORE_ERRORS,
                bounce: false
            }(
                root_,
                amount
            );
        }

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

function acceptTransfer(
        uint128 amount,
        address sender,
        address remainingGasTo,
        bool notify,
        TvmCell payload
    )
        override
        external
        functionID(0x67A0B95F)
    {
        // Проверка адреса контракта-отправителя
        require(msg.sender == address(tvm.hash(_buildWalletInitData(sender))), TokenErrors.SENDER_IS_NOT_VALID_WALLET);

        tvm.rawReserve(_reserve(), 2);

        balance_ += amount;

        if (notify) {
            IAcceptTokensTransferCallback(owner_).onAcceptTokensTransfer{
                value: 0,
                flag: TokenMsgFlag.ALL_NOT_RESERVED + TokenMsgFlag.IGNORE_ERRORS,
                bounce: false
            }(
                root_,
                amount,
                sender,
                msg.sender,
                remainingGasTo,
                payload
            );
        } else {
            remainingGasTo.transfer({ value: 0, flag: TokenMsgFlag.ALL_NOT_RESERVED + TokenMsgFlag.IGNORE_ERRORS, bounce: false });
        }
    }

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

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

Создание кошельков и валидация доступных к взаимодействию акторов

В Everscale адрес смарт-контракта вычисляется следующим образом:
hash(код контракта + static переменные)

function _buildWalletInitData(address walletOwner) override internal view returns (TvmCell) {
        return tvm.buildStateInit({
            contr: TokenWallet,
            varInit: {
                // код TokenRoot
                root_: address(this),
                owner_: walletOwner
            },
            pubkey: 0,
            code: walletCode_
        });

Данный метод есть как у контракта TokenRoot, так и у контракта TokenWallet.

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

Благодаря такой реализации не требуется взаимодействие со смарт-контрактом самого взаимозаменяемого токена, как это происходит в Ethereum:

Помимо прочего, пользователь приложения-кошелька может быть уверен в том, что он получает корректные токены, в противном случае, все пользователи данного приложения будут получать токены другого TokenRoot контракта, что моментально подсветит ошибку в интеграции токена.

Распределенные вычисления – реализация механизмов масштабирования Everscale

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

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

Однако, при росте нагрузки синхронные сети не могут увеличить количество единовременно валидирующих нод и распараллелить нагрузку. В Everscale и Venom – асинхронных блокчейнах – шарды могут разделяться на несколько, и количество шардов в одном воркчейне может расти до 256 единиц. Соответственно, на обработку транзакций может быть одновременно выделено до 256 валидаторов.

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

Колбеки для более гибкого программирования аккаунтов

При разработке собственных решений с использованием контрактов TokenRoot и TokenWallet имеется возможность прописать более гибкую логику работы контрактов после исполнения транзакций получения средств, отправки bounce сообщения об ошибке обработки трансфера, успешного минта или сжигания токенов. Для реализации подобного функционала необходимо воспользоваться интерфейсами, описывающими колбеки.

Тщательный контроль за объемом данных в блокчейне

Поскольку баланс отдельно взятого токена – смарт-контракт, принадлежащий отдельному пользователю, – у TokenRoot контракта нет нужды в хранении адреса каждого держателя токенов.

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

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

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

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

Комментарии (4)


  1. house2008
    04.08.2023 04:25

    Ничего не понятно, но очень интересно) Спасибо. Перестал следить за эфиром, не подскажите, чтобы перевести человеку на кофе 5$ так и нужно отдать до трети этой суммы за перевод или уже починили ? В некоторых сетях цена перевода несколько центов, всё жду это на эфире чтобы начать им активно пользоваться и попробовать задэплоить свой смарт-контракт.


    1. aweawem
      04.08.2023 04:25

      Да, теперь всё по-другому, перешли на экологичный PoS и теперь вам нужно отдать x4 этой суммы за комиссию.


    1. broxus Автор
      04.08.2023 04:25

      Добрый день, благодарим за оставленный комментарий!

      Комиссии в сети Ethereum даже после перехода на PoS остаются динамическими и зависят от нагрузки. Конечный пользователь может захотеть оплатить кофе в период, когда сеть сильно загружена (например, хайп новой монеты и, соответственно, большая нагрузка на дексах), в таком случае комиссия может стоить несколько чашек кофе.

      Если вы хотите попробовать написать собственный смарт-контракт, мы могли бы посоветовать вам обратить внимание и на другие сети.


  1. 03V_N04
    04.08.2023 04:25

    Спасибо за статью! Трудновато с ходу понять, конечно, но анимации очень выручают. Вопрос по поводу удаления контракт-кошелька из блокчейна для контроля за объемом данных: о таких удаленных кошельках вообще не остается никаких записей, их как будто и не было?