Принятие 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_
});
// деплой TokenWallet на базе стейта, преобразованного в TVM-ячейку
function _deployWallet(TvmCell initData, uint128 deployWalletValue, address)
override
internal
view
returns (address)
{
address tokenWallet = new TokenWallet {
stateInit: initData,
value: deployWalletValue,
flag: TokenMsgFlag.SENDER_PAYS_FEES,
code: walletCode_
}();
return tokenWallet;
}
Данные методы есть как у контракта TokenRoot
, так и у контракта TokenWallet
.
Таким образом, и контракт-отправитель и контракт-получатель знают код друг друга, потому что он захеширован в адреса, и, следовательно, оба контракта знают токены какого TokenRoot
будут передаваться и что токены данного TokenRoot
получены правомерно:
Благодаря такой реализации не требуется взаимодействие со смарт-контрактом самого взаимозаменяемого токена, как это происходит в Ethereum:
Помимо прочего, пользователь приложения-кошелька может быть уверен в том, что он получает корректные токены, в противном случае, все пользователи данного приложения будут получать токены другого TokenRoot
контракта, что моментально подсветит ошибку в интеграции токена.
Распределенные вычисления – реализация механизмов масштабирования Everscale
Каждый пользовательский баланс в том или ином токене – отдельный смарт-контракт, что означает, что при высокой нагрузке на сеть и необходимости в обработке транзакций в нескольких шардах, транзакции данных смарт-контрактов могут быть обработаны разными узлами-валидаторами, что и приводит к масштабируемости.
Пока нагрузка на сеть не высока, все смарт-контракты могут находится в одном шарде, а их транзакции валидироваться одним валидатором. Похожая ситуация, только в большем масштабе, справедлива для всех синхронных блокчейнов, где весь блокчейн представляет собой один шард в асинхронной сети:
Однако, при росте нагрузки синхронные сети не могут увеличить количество единовременно валидирующих нод и распараллелить нагрузку. В Everscale и Venom – асинхронных блокчейнах – шарды могут разделяться на несколько, и количество шардов в одном воркчейне может расти до 256 единиц. Соответственно, на обработку транзакций может быть одновременно выделено до 256 валидаторов.
В анимации ниже демонстрируется, как при выскокой нагрузке на сеть смарт-контракты TokenWallet
обмениваются сообщениям со смарт-контрактами из других шардов – результирующие транзакции обрабатываются нодами из соответствующих групп с учетом логического времени транзакций:
Колбеки для более гибкого программирования аккаунтов
При разработке собственных решений с использованием контрактов TokenRoot и TokenWallet имеется возможность прописать более гибкую логику работы контрактов после исполнения транзакций получения средств, отправки bounce сообщения об ошибке обработки трансфера, успешного минта или сжигания токенов. Для реализации подобного функционала необходимо воспользоваться интерфейсами, описывающими колбеки.
Тщательный контроль за объемом данных в блокчейне
Поскольку баланс отдельно взятого токена – смарт-контракт, принадлежащий отдельному пользователю, – у TokenRoot
контракта нет нужды в хранении адреса каждого держателя токенов.
Токены ERC-20, в свою очередь, хранят данные о каждом держателе, что приводит к бесконечному накоплению данных и бесконечному росту физического размера блокчейна:
Если отдельно взятый контракт-кошелек перестает использоваться, рано или поздно средства на оплату storageFee
закончатся, и контракт будет сначала заморожен, а после и вовсе удален из блокчейна. Таким образом отчасти решается проблема бесконечного накопления данных в блокчейне:
С течением массового принятия блокчейн-технологий в будущем требования к масштабируемости сетей будут только ужесточаться: возможность асинхронного исполнения операций с токенами открывает возможности к постройке системы CBDC на хорошо масштабируемом блокчейне.
Кроме того, более привычные операции со стейблкоинами он-чейн также могут потребовать большей пропускной способности сети при значительном росте числа пользователей. Очевидно, что существующие L1 решения с синхронной архитектурой не смогут обеспечить должной пропускной способности и, как следствие, в подобных блокчейнах будут исползоваться механизмы по снижению спроса на услуги сети, результатом работы которых может, например, стать чрезмерное повышение сетевых комиссий.