Токены

Слово "токен" происходит от староанглийского "tācen", что означает знак или символ. Оно обычно используется для обозначения выпущенных в частном порядке специальных монетовидных предметов с незначительной внутренней стоимостью, таких как транспортные токены, токены для прачечных и токены для игровых автоматов.

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

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

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

Как используются токены

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

Валюта

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

Ресурс

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

Активы

Токен может представлять право собственности на внутренний или внешний, материальный или нематериальный актив; например, золото, недвижимость, автомобиль, нефть, энергию, предметы MMOG и т.д.

Доступ

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

Капитал

Токен может представлять акционерный капитал в цифровой организации (например, DAO) или юридическом лице (например, корпорации).

Голосование

Токен может представлять права голоса в цифровой или юридической системе.

Коллекционирование

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

Идентичность

Токен может представлять собой цифровую идентификацию (например, аватар) или юридическую идентификацию (например, национальный идентификатор).

Аттестация

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

Коммунальное хозяйство

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

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

Токены и легковесность

Википедия говорит: "В экономике взаимозаменяемость - это свойство блага или товара, отдельные единицы которого по существу взаимозаменяемы".

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

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

Несущественные токены - это токены, каждый из которых представляет уникальный материальный или нематериальный предмет и поэтому не является взаимозаменяемым. Например, токен, представляющий право собственности на конкретную картину Ван Гога, не эквивалентен другому токену, представляющему Пикассо, даже если они могут быть частью одной и той же системы "токенов собственности на искусство". Аналогично, токен, представляющий конкретный цифровой коллекционный предмет, такой как конкретный CryptoKitty, не является взаимозаменяемым с любым другим CryptoKitty. Каждый неиграбельный токен связан с уникальным идентификатором, таким как серийный номер.

Мы увидим примеры как сменных, так и несменных токенов позже в этой главе.

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

Риск контрагента

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

Токены и интринсикальность

Слово "внутренний" происходит от латинского "intra", что означает "изнутри".

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

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

Одним из наиболее важных последствий использования токенов на основе блокчейна является возможность конвертировать внешние активы в внутренние и тем самым устранить риск контрагента. Хорошим примером является переход от акций корпорации (внешние активы) к акциям или токенам с правом голоса в DAO или аналогичной организации (внутренние активы).

Использование токенов: Утилита или капитал

Почти все проекты в Ethereum сегодня запускаются с каким-либо токеном. Но действительно ли все эти проекты нуждаются в токенах? Есть ли какие-либо недостатки в использовании токенов, или мы увидим, как лозунг "токенизировать все вещи" воплотится в жизнь? В принципе, использование токенов можно рассматривать как конечный инструмент управления или организации. На практике интеграция блокчейн-платформ, включая Ethereum, в существующие структуры общества означает, что пока существует множество ограничений для их применимости.

Давайте начнем с разъяснения роли токена в новом проекте. В большинстве проектов токены используются одним из двух способов: либо как "полезные токены", либо как "токены акций". Очень часто эти две роли смешиваются.

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

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

Это утка!

Многие стартапы сталкиваются с трудной проблемой: токены - отличный механизм привлечения средств, но предложение ценных бумаг (акционерного капитала) общественности является регулируемой деятельностью в большинстве юрисдикций. Замаскировав токены акций под токены услуг, многие стартапы надеются обойти эти регуляторные ограничения и привлечь деньги от публичного размещения, представив его как предварительную продажу "ваучеров доступа к услугам" или, как мы их называем, токенов услуг. Удастся ли этим тонко замаскированным предложениям акций обойти регуляторов, пока неизвестно.

Как гласит народная поговорка: "Если он ходит как утка и крякает как утка, то это утка". Регуляторы вряд ли будут отвлекаться на эти семантические извращения; скорее наоборот, они будут рассматривать такую юридическую софистику как попытку обмануть общественность.

Коммунальные токены: Кому они нужны?

Реальная проблема заключается в том, что токены полезности создают значительные риски и барьеры для принятия стартапами. Возможно, в далеком будущем "токенизировать все вещи" станет реальностью, но в настоящее время набор людей, которые имеют представление о токене и желание его использовать, является подмножеством и без того небольшого криптовалютного рынка.

Для стартапа каждая инновация представляет собой риск и рыночный фильтр. Инновация - это путь, который наименее проходим, уход от традиций. Это уже одинокий путь. Если стартап пытается внедрить инновацию в новую область технологии, например, обмен хранилищами данных через сети P2P, это достаточно одинокий путь. Добавление к этой инновации токена полезности и требование от пользователей принять токены, чтобы воспользоваться услугой, усугубляет риск и увеличивает барьеры для принятия. Это сходит с и без того одинокой тропы инноваций в области хранения данных P2P в пустыню.

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

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

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

Наконец, в начале этой главы, когда мы представляли токены, мы обсудили разговорное значение слова "жетон" как "нечто, имеющее незначительную ценность". Основная причина незначительной ценности большинства токенов заключается в том, что они могут быть использованы только в очень узком контексте: в одной автобусной компании, одной прачечной, одном зале игровых автоматов, одном отеле или одном фирменном магазине. Ограниченная ликвидность, ограниченная применимость и высокие затраты на конвертацию снижают ценность токенов до тех пор, пока они не будут иметь только "токенную" ценность. Поэтому, когда вы добавляете токен полезности на свою платформу, но этот токен может быть использован только на вашей единственной платформе с небольшим рынком, вы воссоздаете условия, которые сделали физические токены бесполезными. Возможно, это действительно правильный способ внедрения токенизации в ваш проект. Однако если для того, чтобы воспользоваться вашей платформой, пользователь должен конвертировать что-то в ваш полезный токен, использовать его, а затем конвертировать остаток обратно во что-то более общеполезное, вы создали компанию-скрипт. Затраты на переключение цифрового токена на порядки ниже, чем у физического токена без рынка, но они не равны нулю. Утилитарные токены, которые работают во всех отраслях промышленности, будут очень интересными и, возможно, довольно ценными. Но если вы настроите свой стартап на то, что для достижения успеха вам придется задействовать целый отраслевой стандарт, то, возможно, вы уже потерпели неудачу.

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

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

Токены на Ethereum

Блокчейн-токены существовали и до Ethereum. В некотором смысле первая валюта блокчейна, биткоин, сама является токеном. Многие платформы токенов также были разработаны на базе Bitcoin и других криптовалют до Ethereum. Однако введение первого стандарта токенов на Ethereum привело к бурному росту числа токенов.

Виталик Бутерин предложил токены как одно из наиболее очевидных и полезных применений обобщенного программируемого блокчейна, такого как Ethereum. На самом деле, в первый год существования Ethereum часто можно было увидеть Виталика и других людей в футболках с логотипом Ethereum и образцом смарт-контракта на спине. Существовало несколько вариантов этой футболки, но на самой распространенной была изображена реализация токена. Прежде чем мы углубимся в детали создания токенов на Ethereum, важно получить общее представление о том, как работают токены на Ethereum. Токены отличаются от эфира тем, что протокол Ethereum ничего о них не знает. Отправка эфира является неотъемлемым действием платформы Ethereum, а отправка или даже владение токенами - нет. Баланс эфира на счетах Ethereum обрабатывается на уровне протокола, тогда как баланс токенов на счетах Ethereum обрабатывается на уровне смарт-контракта. Для того чтобы создать новый токен в Ethereum, необходимо создать новый смарт-контракт. После развертывания смарт-контракт управляет всем, включая владение, передачу и права доступа. Вы можете написать свой смарт-контракт для выполнения всех необходимых действий любым способом, но, вероятно, разумнее всего следовать существующему стандарту. Мы рассмотрим такие стандарты далее. В конце главы мы обсудим плюсы и минусы следующих стандартов.

Стандарт токенов ERC20

Первый стандарт был представлен в ноябре 2015 года Фабианом Фогельштеллером в виде запроса на комментарии (ERC) Ethereum. Ему был автоматически присвоен номер выпуска 20 на GitHub, что дало название "токен ERC20". В настоящее время подавляющее большинство токенов основано на стандарте ERC20. Запрос на комментарии ERC20 в конечном итоге превратился в Ethereum Improvement Proposal 20 (EIP-20), но в основном он по-прежнему упоминается под первоначальным названием ERC20.

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

Необходимые функции и события ERC20

Контракт токенов, соответствующий стандарту ERC20, должен обеспечивать как минимум следующие функции и события: totalSupply Возвращает общее количество единиц данного токена, которые существуют в настоящее время. Токены ERC20 могут иметь фиксированный или переменный запас. балансОф Учитывая адрес, возвращает баланс токенов по этому адресу. перевод Учитывая адрес и сумму, переводит это количество токенов на этот адрес с баланса адреса, выполнившего перевод. transferFrom Учитывая отправителя, получателя и сумму, переводит токены с одного счета на другой. Используется в сочетании с approve. утвердить Учитывая адрес получателя и сумму, дает разрешение на выполнение нескольких переводов на эту сумму со счета, выдавшего разрешение. пособие Учитывая адрес владельца и адрес расходующего, возвращает оставшуюся сумму, которую расходующий имеет право снять с владельца. Передача Событие, срабатывающее при успешном переводе (вызов transfer или transferFrom) (даже для переводов с нулевым значением). Утверждение Событие, регистрируемое при успешном вызове утверждения.

Дополнительные функции ERC20

В дополнение к обязательным функциям, перечисленным в предыдущем разделе, стандартом также определены следующие необязательные функции: имя Возвращает человекочитаемое имя (например, "Доллары США") маркера. символ Возвращает человекочитаемый символ (например, "USD") для маркера. десятичные дроби Возвращает количество десятичных дробей, используемых для деления суммы токенов. Например, если decimals равно 2, то сумма токенов делится на 100, чтобы получить ее пользовательское представление.

####Интерфейс ERC20, определенный в Solidity

Вот как выглядит спецификация интерфейса ERC20 в Solidity:

contract ERC20 {
   function totalSupply() constant returns (uint theTotalSupply);
   function balanceOf(address _owner) constant returns (uint balance);
   function transfer(address _to, uint _value) returns (bool success);
   function transferFrom(address _from, address _to, uint _value) returns
      (bool success);
   function approve(address _spender, uint _value) returns (bool success);
   function allowance(address _owner, address _spender) constant returns
      (uint remaining);
   event Transfer(address indexed _from, address indexed _to, uint _value);
   event Approval(address indexed _owner, address indexed _spender, uint _value);
}

Структуры данных ERC20

Если вы изучите любую реализацию ERC20, то увидите, что она содержит две структуры данных, одна для отслеживания остатков, другая для отслеживания надбавок. В Solidity они реализованы с помощью отображения данных.

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

mapping(address => uint256) balances;

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

mapping (address => mapping (address => uint256)) public allowed;

Рабочие процессы ERC20: "передача" и "утверждение и передача".

Стандарт токенов ERC20 имеет две функции передачи. Вам может быть интересно, почему.

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

Выполнение контракта на передачу очень простое. Если Алиса хочет отправить 10 токенов Бобу, ее кошелек посылает транзакцию на адрес контракта на передачу токенов, вызывая функцию передачи с адресом Боба и 10 в качестве аргументов. Контракт на токены корректирует баланс Алисы (-10) и баланс Боба (+10) и выдает событие Transfer.

Второй рабочий процесс - это рабочий процесс с двумя транзакциями, в котором используется approve, а затем transferFrom. Этот рабочий процесс позволяет владельцу токена делегировать свой контроль другому адресу. Чаще всего он используется для передачи контроля контракту на распределение токенов, но может применяться и биржами.

Например, если компания продает токены для ICO, она может утвердить адрес контракта краудсейла для распределения определенного количества токенов. Затем контракт краудсейла может перевести средства с баланса владельца контракта токена каждому покупателю токена, как показано в документе "Двухэтапный рабочий процесс утверждения и перевода токенов ERC20".

Примечание: Первичное предложение монет (ICO) - это механизм краудфандинга, используемый компаниями и организациями для привлечения средств путем продажи токенов. Этот термин происходит от Initial Public Offering (IPO) - процесса, в ходе которого публичная компания предлагает акции для продажи инвесторам на фондовой бирже. В отличие от строго регулируемых рынков IPO, ICO являются открытыми, глобальными и беспорядочными. Примеры и объяснения ICO, приведенные в этой книге, не являются одобрением этого вида привлечения средств.

Двухэтапный рабочий процесс утверждения и передачи токенов ERC20 с помощью TransferFrom Рисунок 1. Двухэтапный рабочий процесс утверждения и передачи токенов ERC20 с помощью TransferFrom

Для рабочего процесса approve & transferFrom необходимы две транзакции. Допустим, Алиса хочет разрешить контракту AliceICO продать 50% всех токенов AliceCoin таким покупателям, как Боб и Чарли. Сначала Алиса запускает контракт AliceCoin ERC20, выпуская все AliceCoin на свой собственный адрес. Затем Алиса запускает контракт AliceICO, который может продать токены за эфир. Далее Алиса инициирует рабочий процесс approve & transferFrom. Она отправляет транзакцию контракту AliceCoin, вызывая команду approve с указанием адреса контракта AliceICO и 50% от общей суммы TotalSupply в качестве аргументов. Это вызовет событие Approval. Теперь контракт AliceICO может продавать AliceCoin.

Когда контракт AliceICO получает эфир от Боба, ему необходимо отправить Бобу некоторое количество AliceCoin в ответ. Внутри контракта AliceICO установлен обменный курс между AliceCoin и эфиром. Курс обмена, который Алиса установила при создании контракта AliceICO, определяет, сколько токенов получит Боб за количество эфира, отправленное контракту AliceICO. Когда контракт AliceICO вызывает функцию AliceCoin transferFrom, он устанавливает адрес Алисы в качестве отправителя и адрес Боба в качестве получателя и использует обменный курс для определения того, сколько токенов AliceCoin будет передано Бобу в поле value. Контракт AliceCoin переводит баланс с адреса Алисы на адрес Боба и вызывает событие Transfer. Контракт AliceICO может вызывать transferFrom неограниченное количество раз, если он не превышает установленный Алисой лимит одобрения. Контракт AliceICO может отслеживать, сколько токенов AliceCoin он может продать, вызывая функцию allowance.

Реализации ERC20

Хотя токен, совместимый с ERC20, можно реализовать примерно в 30 строках кода Solidity, большинство реализаций более сложны. Это необходимо для учета потенциальных уязвимостей безопасности. В стандарте EIP-20 упоминаются две реализации:

Consensys EIP20 (http://bit.ly/2EUYCMR) Простая и легко читаемая реализация токена, совместимого с ERC20.

OpenZeppelin StandardToken (https://bit.ly/2xPYck6) Эта реализация совместима с ERC20 с дополнительными мерами предосторожности. Она составляет основу библиотек OpenZeppelin, реализующих более сложные ERC20-совместимые токены с лимитами сбора средств, аукционами, графиками наделения правами и другими функциями.

Запуск собственного токена ERC20

Давайте создадим и запустим наш собственный токен. Для этого примера мы будем использовать фреймворк Truffle. В примере предполагается, что вы уже установили и настроили truffle и знакомы с его основными принципами работы (подробнее см. [truffle]).

Мы назовем наш токен "Mastering Ethereum Token", с символом "MET".

Примечание: Вы можете найти этот пример в репозитории GitHub книги.

Сначала создадим и инициализируем каталог проекта Truffle. Выполните эти четыре команды и примите ответы по умолчанию на все вопросы:

$ mkdir METoken
$ cd METoken
METoken $ truffle init
METoken $ npm init

Теперь у вас должна быть следующая структура каталогов:

METoken/
+---- contracts
|   `---- Migrations.sol
+---- migrations
|   `---- 1_initial_migration.js
+---- package.json
+---- test
`---- truffle-config.js

Отредактируйте файл конфигурации truffle-config.js, чтобы настроить окружение Truffle, или скопируйте его из репозитория.

Если вы используете пример truffle-config.js, не забудьте создать файл .env в папке METoken, содержащий ваши тестовые приватные ключи для тестирования и развертывания в публичных тестовых сетях Ethereum, таких как Ropsten или Kovan. Вы можете экспортировать приватный ключ тестовой сети из MetaMask.

После этого ваш каталог должен выглядеть следующим образом:

METoken/
+---- contracts
|   `---- Migrations.sol
+---- migrations
|   `---- 1_initial_migration.js
+---- package.json
+---- test
+---- truffle-config.js
`---- .env *new file*

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

Для нашего примера мы импортируем библиотеку OpenZeppelin, которая реализует некоторые важные проверки безопасности и легко расширяется:

$ npm install openzeppelin-solidity@1.12.0

+ openzeppelin-solidity@1.12.0
added 1 package from 1 contributor and audited 2381 packages in 4.074s

Пакет openzeppelin-solidity добавит около 250 файлов в каталог node_modules. Библиотека OpenZeppelin включает в себя гораздо больше, чем токен ERC20, но мы будем использовать только небольшую часть.

Далее давайте напишем наш контракт на токены. Создайте новый файл METoken.sol и скопируйте код примера с GitHub.

Наш контракт, показанный в файле METoken.sol: Solidity contract implementing an ERC20 token, очень прост, поскольку он наследует всю свою функциональность от библиотеки OpenZeppelin.

Пример 1. METoken.sol: Контракт Solidity, реализующий токен ERC20 link:code/truffle/METoken/contracts/METoken.sol[]

Здесь мы определяем необязательные переменные name, symbol и decimals. Мы также определяем переменную _initial_supply, установленную на 21 миллион жетонов; с двумя десятичными знаками подразделения это дает 2,1 миллиарда общих единиц. В функции инициализации (конструкторе) контракта мы устанавливаем totalSupply равным _initial_supply и выделяем все _initial_supply на баланс счета (msg.sender), который создает контракт METoken.

Теперь мы используем truffle для компиляции кода METoken:

$ truffle compile
Compiling ./contracts/METoken.sol...
Compiling ./contracts/Migrations.sol...
Compiling openzeppelin-solidity/contracts/math/SafeMath.sol...
Compiling openzeppelin-solidity/contracts/token/ERC20/BasicToken.sol...
Compiling openzeppelin-solidity/contracts/token/ERC20/ERC20.sol...
Compiling openzeppelin-solidity/contracts/token/ERC20/ERC20Basic.sol...
Compiling openzeppelin-solidity/contracts/token/ERC20/StandardToken.sol...

Как вы можете видеть, truffle включает необходимые зависимости из библиотек OpenZeppelin и компилирует эти контракты.

Давайте настроим сценарий миграции для развертывания контракта METoken. Создайте новый файл под названием 2_deploy_contracts.js в папке METoken/migrations. Скопируйте его содержимое из примера в репозитории GitHub:

2_deploy_contracts: Миграция на развертывание METoken

link:code/truffle/METoken/migrations/2_deploy_contracts.js[]

Перед развертыванием в одной из тестовых сетей Ethereum давайте запустим локальный блокчейн, чтобы все проверить. Запустите блокчейн ganache либо из командной строки с помощью ganache-cli, либо из графического интерфейса пользователя.

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

$ truffle migrate --network ganache
Using network 'ganache'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0xb2e90a056dc6ad8e654683921fc613c796a03b89df6760ec1db1084ea4a084eb
  Migrations: 0x8cdaf0cd259887258bc13a92c0a6da92698644c0
Saving successful migration to network...
  ... 0xd7bc86d31bee32fa3988f1c1eabce403a1b5d570340a3a9cdba53a472ee8c956
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Deploying METoken...
  ... 0xbe9290d59678b412e60ed6aefedb17364f4ad2977cfb2076b9b8ad415c5dc9f0
  METoken: 0x345ca3e014aaf5dca488057592ee47305d9b3e10
Saving successful migration to network...
  ... 0xf36163615f41ef7ed8f4a8f192149a0bf633fe1a2398ce001bf44c43dc7bdda0
Saving artifacts...

На консоли ganache мы должны увидеть, что наше развертывание создало четыре новые транзакции, как показано в METoken deployment on ganache.

Развертывание METoken на ganache Рисунок 2. Развертывание METoken на ganache

Взаимодействие с METoken с помощью консоли Truffle

Мы можем взаимодействовать с нашим контрактом на блокчейне ganache с помощью консоли Truffle. Это интерактивная среда JavaScript, которая обеспечивает доступ к среде Truffle и, через web3, к блокчейну. В данном случае мы подключим консоль Truffle к блокчейну ganache:

$ truffle console --network ganache
truffle(ganache)>

Подсказка truffle(ganache)> показывает, что мы подключены к блокчейну ganache и готовы вводить наши команды. Консоль Truffle поддерживает все команды truffle, поэтому мы можем компилировать и мигрировать из консоли. Мы уже выполнили эти команды, поэтому давайте перейдем непосредственно к самому контракту. Контракт METoken существует как объект JavaScript в среде Truffle. Введите METoken в подсказку, и она выгрузит все определение контракта:

truffle(ganache)> METoken
{ [Function: TruffleContract]
  _static_methods:

[...]

currentProvider:
 HttpProvider {
   host: 'http://localhost:7545',
   timeout: 0,
   user: undefined,
   password: undefined,
   headers: undefined,
   send: [Function],
   sendAsync: [Function],
   _alreadyWrapped: true },
network_id: '5777' }

Объект METoken также раскрывает несколько атрибутов, таких как адрес контракта (развернутый командой migrate):

truffle(ganache)> METoken.address
'0x345ca3e014aaf5dca488057592ee47305d9b3e10'

Если мы хотим взаимодействовать с развернутым контрактом, мы должны использовать асинхронный вызов в форме "обещания" JavaScript. Мы используем развернутую функцию для получения экземпляра контракта, а затем вызываем функцию totalSupply:

truffle(ganache)> METoken.deployed().then(instance => instance.totalSupply())
BigNumber { s: 1, e: 9, c: [ 2100000000 ] }

Далее, давайте воспользуемся счетами, созданными ganache, чтобы проверить баланс METoken и отправить несколько METoken на другой адрес. Сначала давайте получим адреса счетов:

truffle(ganache)> let accounts
undefined
truffle(ganache)> web3.eth.getAccounts((err,res) => { accounts = res })
undefined
truffle(ganache)> accounts[0]
'0x627306090abab3a6e1400e9345bc60c78a8bef57'

Теперь список учетных записей содержит все учетные записи, созданные ganache, а учетная запись[0] - это учетная запись, которая развернула контракт METoken. У него должен быть баланс METoken, потому что наш конструктор METoken отдает весь запас токенов адресу, который его создал. Давайте проверим:

truffle(ganache)> METoken.deployed().then(instance =>
                  { instance.balanceOf(accounts[0]).then(console.log) })
undefined
truffle(ganache)> BigNumber { s: 1, e: 9, c: [ 2100000000 ] }

Наконец, давайте переведем 1000.00 METoken со счета[0] на счет[1], вызвав функцию перевода контракта:

truffle(ganache)> METoken.deployed().then(instance =>
                  { instance.transfer(accounts[1], 100000) })
undefined
truffle(ganache)> METoken.deployed().then(instance =>
                  { instance.balanceOf(accounts[0]).then(console.log) })
undefined
truffle(ganache)> BigNumber { s: 1, e: 9, c: [ 2099900000 ] }
undefined
truffle(ganache)> METoken.deployed().then(instance =>
                  { instance.balanceOf(accounts[1]).then(console.log) })
undefined
truffle(ganache)> BigNumber { s: 1, e: 5, c: [ 100000 ] }

Совет: МЕТОКЕН имеет точность 2 десятичных знака, что означает, что 1 МЕТОКЕН равен 100 единицам в контракте. Когда мы передаем 1 000 METoken, мы указываем значение 100000 в вызове функции передачи.

Как вы можете видеть в консоли, счета[0] теперь имеют 20 999 000 MET, а счета[1] - 1 000 MET.

Если вы переключитесь на графический интерфейс пользователя ganache, как показано в METoken transfer на ganache, вы увидите транзакцию, которая вызвала функцию трансфера.

Перенос метокена на ганаш Рисунок 3. Перенос метокена на ганаш

Отправка токенов ERC20 на адреса контрактов

На данный момент мы создали токен ERC20 и перевели несколько токенов с одного счета на другой. Все счета, которые мы использовали для этих демонстраций, принадлежат внешним пользователям, то есть они контролируются закрытым ключом, а не контрактом. Что произойдет, если мы отправим MET на адрес контракта? Давайте узнаем!

Сначала давайте развернем еще один контракт в нашей тестовой среде. Для этого примера мы будем использовать наш первый контракт Faucet.sol. Давайте добавим его в проект METoken, скопировав его в каталог contracts. Наша директория должна выглядеть следующим образом:

METoken/
+---- contracts
|   +---- Faucet.sol
|   +---- METoken.sol
|   `---- Migrations.sol

Мы также добавим миграцию, чтобы развернуть Faucet отдельно от METoken:

var Faucet = artifacts.require("Faucet");

module.exports = function(deployer) {
  // Deploy the Faucet contract as our only task
  deployer.deploy(Faucet);
};

Давайте скомпилируем и перенесем контракты из консоли Truffle:

$ truffle console --network ganache
truffle(ganache)> compile
Compiling ./contracts/Faucet.sol...
Writing artifacts to ./build/contracts

truffle(ganache)> migrate
Using network 'ganache'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0x89f6a7bd2a596829c60a483ec99665c7af71e68c77a417fab503c394fcd7a0c9
  Migrations: 0xa1ccce36fb823810e729dce293b75f40fb6ea9c9
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Replacing METoken...
  ... 0x28d0da26f48765f67e133e99dd275fac6a25fdfec6594060fd1a0e09a99b44ba
  METoken: 0x7d6bf9d5914d37bcba9d46df7107e71c59f3791f
Saving artifacts...
Running migration: 3_deploy_faucet.js
  Deploying Faucet...
  ... 0x6fbf283bcc97d7c52d92fd91f6ac02d565f5fded483a6a0f824f66edc6fa90c3
  Faucet: 0xb18a42e9468f7f1342fa3c329ec339f254bc7524
Saving artifacts...

Отлично. Теперь давайте отправим несколько MET на контракт с Faucet:

truffle(ganache)> METoken.deployed().then(instance =>
                  { instance.transfer(Faucet.address, 100000) })
truffle(ganache)> METoken.deployed().then(instance =>
                  { instance.balanceOf(Faucet.address).then(console.log)})
truffle(ganache)> BigNumber { s: 1, e: 5, c: [ 100000 ] }

Итак, мы перевели 1 000 MET на контракт Faucet. Теперь, как нам вывести эти токены?

Помните, Faucet.sol - это довольно простой контракт. У него есть только одна функция, withdraw, которая предназначена для вывода эфира. У него нет функции для вывода MET или любого другого токена ERC20. Если мы используем функцию withdraw, она попытается отправить эфир, но поскольку у Faucet еще нет баланса эфира, она потерпит неудачу.

Контракт METoken знает, что у Faucet есть баланс, но единственный способ, которым он может перевести этот баланс, - это получить вызов передачи с адреса контракта. Каким-то образом мы должны заставить контракт Faucet вызвать функцию передачи в METoken.

Если вы задаетесь вопросом, что делать дальше, не делайте этого. Решения этой проблемы не существует. МЕТ, отправленный в Faucet, застрял навсегда. Только контракт Faucet может передать его, а контракт Faucet не имеет кода для вызова функции передачи контракта токена ERC20. Возможно, вы предвидели эту проблему. Скорее всего, нет. На самом деле, как и сотни пользователей Ethereum, которые случайно перевели различные токены на контракты, не имеющие возможности работы с ERC20. По некоторым оценкам, токены на сумму более 2,5 миллионов долларов США (на момент написания статьи) "застряли" подобным образом и были потеряны навсегда.

Один из способов, с помощью которого пользователи токенов ERC20 могут непреднамеренно потерять свои токены при переводе, - это попытка перевода на биржу или другой сервис. Они копируют адрес Ethereum с сайта биржи, думая, что могут просто отправить на него токены. Однако многие биржи публикуют адреса приема, которые на самом деле являются контрактами! Эти контракты предназначены только для получения эфира, а не токенов ERC20, и чаще всего они сметают все отправленные на них средства в "холодное хранилище" или на другой централизованный кошелек. Несмотря на многочисленные предупреждения "не отправляйте токены на этот адрес", многие токены теряются таким образом.

Демонстрация рабочего процесса "утвердить и передать" (approve & transferFrom)

Наш контракт Faucet не мог работать с токенами ERC20. Отправка токенов в него с помощью функции передачи приводила к потере этих токенов. Давайте перепишем контракт и сделаем так, чтобы он мог работать с токенами ERC20. В частности, мы превратим его в кран, который выдает MET всем, кто попросит.

Для этого примера мы создадим копию каталога проекта truffle (назовем его METoken_METFaucet), инициализируем truffle и npm, установим зависимости OpenZeppelin и скопируем контракт METoken.sol. Подробные инструкции см. в нашем первом примере, в разделе Запуск собственного токена ERC20.

Наш новый контракт на краны, METFaucet.sol, будет выглядеть как METFaucet.sol: Кран для Метокена.

Пример 2. METFaucet.sol: Кран для METoken link:code/truffle/METoken_METFaucet/contracts/METFaucet.sol[]

Мы внесли довольно много изменений в базовый пример Faucet. Поскольку METFaucet будет использовать функцию transferFrom в METoken, ему понадобятся две дополнительные переменные. В одной будет храниться адрес развернутого контракта METoken. Другая будет содержать адрес владельца MET, который будет утверждать снятие средств с крана. Контракт METFaucet вызовет функцию METoken.transferFrom и поручит ей перевести MET от владельца на адрес, с которого поступил запрос на снятие крана.

Мы объявляем эти две переменные здесь:

StandardToken public METoken;
address public METOwner;

Поскольку наш кран должен быть инициализирован с правильными адресами для METoken и METOwner, нам нужно объявить пользовательский конструктор:

// METFaucet constructor - provide the address of the METoken contract and
// the owner address we will be approved to transferFrom
function METFaucet(address _METoken, address _METOwner) public {

    // Initialize the METoken from the address provided
    METoken = StandardToken(_METoken);
    METOwner = _METOwner;
}

Следующее изменение касается функции withdraw. Вместо вызова transfer, METFaucet использует функцию transferFrom в METoken и просит METoken перевести MET получателю крана:

// Use the transferFrom function of METoken
METoken.transferFrom(METOwner, msg.sender, withdraw_amount);

Наконец, поскольку наш кран больше не отправляет эфир, нам, вероятно, следует запретить кому-либо отправлять эфир на METFaucet, поскольку мы не хотим, чтобы он застрял. Мы изменим функцию fallback payable, чтобы отклонять входящие эфиры, используя функцию revert для возврата любых входящих платежей:

// REJECT any incoming ether
function () external payable { revert(); }

Теперь, когда наш код METFaucet.sol готов, нам нужно изменить сценарий миграции для его развертывания. Этот сценарий миграции будет немного сложнее, поскольку METFaucet зависит от адреса METoken. Мы будем использовать обещание JavaScript для последовательного развертывания двух контрактов. Создайте файл 2_deploy_contracts.js следующим образом:

var METoken = artifacts.require("METoken");
var METFaucet = artifacts.require("METFaucet");

module.exports = function(deployer, network, accounts) {

    var owner = accounts[0];
    // Deploy the METoken contract first
    deployer.deploy(METoken, {from: owner}).then(function() {
        // Then deploy METFaucet and pass the address of METoken and the
        // address of the owner of all the MET who will approve METFaucet
        return deployer.deploy(METFaucet, METoken.address, owner);
    });
}

Теперь мы можем протестировать все в консоли Truffle. Сначала мы используем migrate для развертывания контрактов. Когда METoken будет развернут, он выделит все MET на счет, который его создал, web3.eth.accounts[0]. Затем мы вызываем функцию approve в METoken, чтобы утвердить METFaucet для отправки до 1 000 MET от имени web3.eth.accounts[0]. Наконец, чтобы протестировать наш кран, мы вызываем METFaucet.withdraw от web3.eth.accounts[1] и пытаемся снять 10 MET. Вот команды консоли:

$ truffle console --network ganache
truffle(ganache)> migrate
Using network 'ganache'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0x79352b43e18cc46b023a779e9a0d16b30f127bfa40266c02f9871d63c26542c7
  Migrations: 0xaa588d3737b611bafd7bd713445b314bd453a5c8
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Replacing METoken...
  ... 0xc42a57f22cddf95f6f8c19d794c8af3b2491f568b38b96fef15b13b6e8bfff21
  METoken: 0xf204a4ef082f5c04bb89f7d5e6568b796096735a
  Replacing METFaucet...
  ... 0xd9615cae2fa4f1e8a377de87f86162832cf4d31098779e6e00df1ae7f1b7f864
  METFaucet: 0x75c35c980c0d37ef46df04d31a140b65503c0eed
Saving artifacts...
truffle(ganache)> METoken.deployed().then(instance =>
                  { instance.approve(METFaucet.address, 100000) })
truffle(ganache)> METoken.deployed().then(instance =>
                  { instance.balanceOf(web3.eth.accounts[1]).then(console.log) })
truffle(ganache)> BigNumber { s: 1, e: 0, c: [ 0 ] }
truffle(ganache)> METFaucet.deployed().then(instance =>
                  { instance.withdraw(1000, {from:web3.eth.accounts[1]}) })
truffle(ganache)> METoken.deployed().then(instance =>
                  { instance.balanceOf(web3.eth.accounts[1]).then(console.log) })
truffle(ganache)> BigNumber { s: 1, e: 3, c: [ 1000 ] }

Как видно из результатов, мы можем использовать рабочий процесс approve & transferFrom для авторизации одного контракта для передачи токенов, определенных в другом контракте. При правильном использовании токены ERC20 могут использоваться EOA и другими контрактами. Однако бремя правильного управления токенами ERC20 перекладывается на пользовательский интерфейс. Если пользователь неправильно попытается перевести токены ERC20 на адрес контракта, а этот контракт не приспособлен для приема токенов ERC20, токены будут потеряны.

Проблемы с токенами ERC20

Принятие стандарта токенов ERC20 было поистине взрывным. Были выпущены тысячи токенов, как для экспериментов с новыми возможностями, так и для привлечения средств на различных "краудфандинговых" аукционах и ICO. Однако существуют некоторые потенциальные подводные камни, как мы видели в вопросе перевода токенов на адреса контрактов. Одна из менее очевидных проблем с токенами ERC20 заключается в том, что они раскрывают тонкие различия между токенами и самим эфиром. Если эфир переводится транзакцией, в которой в качестве адреса получателя указан адрес получателя, то перевод токенов происходит в рамках конкретного состояния контракта токена, и в качестве адреса получателя указан контракт токена, а не адрес получателя. Контракт на токены отслеживает балансы и выпускает события. При переводе токенов никакая транзакция получателю токена фактически не отправляется. Вместо этого адрес получателя добавляется в карту в самом контракте токена. Транзакция, отправляющая эфир на адрес, изменяет состояние адреса. Транзакция, передающая токен на адрес, изменяет только состояние контракта на токен, но не состояние адреса получателя. Даже кошелек, поддерживающий токены ERC20, не узнает о балансе токенов, пока пользователь явно не добавит конкретный контракт на токены в "наблюдение". Некоторые кошельки следят за наиболее популярными контрактами на токены, чтобы обнаружить остатки на контролируемых ими адресах, но это ограничено небольшой частью существующих контрактов ERC20. На самом деле, маловероятно, что пользователь захочет отслеживать все балансы во всех возможных контрактах на токены ERC20. Многие токены ERC20 больше похожи на почтовый спам, чем на полезные токены. Они автоматически создают балансы для аккаунтов, на которых есть активность в Эфире, чтобы привлечь пользователей. Если у вас есть адрес Ethereum с долгой историей активности, особенно если он был создан в ходе предпродажи, вы обнаружите, что он полон "нежелательных" токенов, появившихся из ниоткуда. Конечно, на самом деле адрес заполнен не токенами, а контрактами на токены, в которых указан ваш адрес. Вы увидите эти остатки только в том случае, если за этими контрактами следит блокчейн-проводник или кошелек, который вы используете для просмотра своего адреса. Токены ведут себя не так, как эфир. Эфир отправляется с помощью функции send и принимается любой оплачиваемой функцией в контракте или любым внешним адресом. Токены отправляются с помощью функций transfer или approve & transferFrom, которые существуют только в контракте ERC20 и не вызывают (по крайней мере, в ERC20) никаких оплачиваемых функций в контракте получателя. Токены должны функционировать так же, как криптовалюта, например, эфир, но они имеют некоторые отличия, которые разрушают эту иллюзию. Рассмотрим еще один вопрос. Чтобы отправить эфир или использовать любой контракт Ethereum, вам нужен эфир для оплаты газа. Чтобы отправить токены, вам также нужен эфир. Вы не можете оплатить газ транзакции токеном, а контракт с токеном не может оплатить газ за вас. Возможно, в отдаленном будущем это изменится, но пока это может привести к довольно странным последствиям для пользователей. Допустим, вы используете биржу или ShapeShift для конвертации биткоина в токен. Вы "получаете" токен в кошелек, который отслеживает контракт этого токена и показывает ваш баланс. Он выглядит так же, как и любая другая криптовалюта, которая есть в вашем кошельке. Однако попробуйте отправить токен, и ваш кошелек сообщит вам, что для этого вам нужен эфир. Вы можете быть озадачены - ведь для получения токена вам не нужен эфир. Возможно, у вас нет эфира. Возможно, вы даже не знали, что токен - это токен ERC20 на Ethereum; возможно, вы думали, что это криптовалюта с собственным блокчейном. Иллюзия просто разбилась. Некоторые из этих вопросов специфичны для токенов ERC20. Другие являются более общими вопросами, связанными с абстракцией и границами интерфейса в Ethereum. Некоторые из них могут быть решены путем изменения интерфейса токенов, в то время как другие могут потребовать изменения фундаментальных структур в Ethereum (например, различие между EOA и контрактами, а также между транзакциями и сообщениями). Некоторые могут быть не совсем "решаемы" и могут потребовать разработки пользовательского интерфейса, чтобы скрыть нюансы и сделать пользовательский опыт последовательным независимо от основных различий. В следующих разделах мы рассмотрим различные предложения, которые пытаются решить некоторые из этих вопросов. ERC223: Предлагаемый стандарт интерфейса интерфейса контракта с токенами Предложение ERC223 пытается решить проблему непреднамеренной передачи токенов контракту (который может поддерживать или не поддерживать токены), определяя, является ли адрес назначения контрактом или нет. ERC223 требует, чтобы контракты, предназначенные для приема токенов, реализовывали функцию tokenFallback. Если адресом назначения перевода является контракт, а контракт не поддерживает токены (т.е. не реализует tokenFallback), перевод не состоится. Чтобы определить, является ли адрес назначения контрактом, эталонная реализация ERC223 использует небольшой сегмент встроенного байткода довольно креативным способом: function isContract(address _addr) private view returns (bool is_contract) { uint length; сборка { // извлекаем размер кода по целевому адресу; для этого нужна сборка length := extcodesize(_addr) } return (length>0); } Спецификация интерфейса контракта ERC223 такова: интерфейс ERC223Token { uint public totalSupply; функция balanceOf(address who) public view возвращает (uint);

function name() public view возвращает (строку _name); function symbol() public view возвращает (строку _symbol); function decimals() public view returns (uint8 _decimals); функция totalSupply() public view возвращает (uint256 _supply);

function transfer(address to, uint value) public returns (bool ok); function transfer(address to, uint value, bytes data) public returns (bool ok); функция transfer(address to, uint value, bytes data, string custom_fallback) public returns (bool ok);

событие Transfer(адрес, индексированный from, адрес, индексированный to, uint value, байт индексированных данных); } ERC223 не имеет широкого распространения, и в ветке обсуждения ERC ведутся дебаты об обратной совместимости и компромиссах между реализацией изменений на уровне интерфейса контракта и пользовательского интерфейса. Дебаты продолжаются. ERC777: Предлагаемый стандарт интерфейса интерфейса контракта с токенами Еще одним предложением по улучшенному стандарту токен-контрактов является ERC777. Это предложение преследует несколько целей, в том числе: Чтобы предложить интерфейс, совместимый с ERC20 Передача токенов с помощью функции отправки, аналогично передаче эфира Быть совместимым с ERC820 для регистрации токен-контрактов Чтобы позволить контрактам и адресам контролировать, какие токены они отправляют, с помощью функции tokensToSend, которая вызывается перед отправкой Обеспечить возможность уведомления контрактов и адресов о получении токенов путем вызова функции tokensReceived в получателе, а также снизить вероятность блокировки токенов в контрактах, требуя от контрактов предоставления функции tokensReceived Чтобы позволить существующим контрактам использовать прокси-контракты для функций tokensToSend и tokensReceived Действовать одинаково, независимо от того, отправляется ли договор или EOA Обеспечить конкретные события для чеканки и сжигания жетонов Чтобы дать возможность операторам (доверенным третьим лицам, предназначенным для верифицированных контрактов) перемещать токены от имени держателя токенов Предоставление метаданных о транзакциях передачи токенов в полях userData и operatorData Текущее обсуждение ERC777 можно найти на GitHub. Спецификация интерфейса контракта ERC777: интерфейс ERC777Token { функция name() public constant returns (string); функция symbol() public constant returns (string); функция totalSupply() public constant returns (uint256); функция granularity() public constant returns (uint256); function balanceOf(адрес владельца) public constant returns (uint256);

function send(address to, uint256 amount, bytes userData) public;

function authorizeOperator(address operator) public;
function revokeOperator(address operator) public;
функция isOperatorFor(address operator, address tokenHolder)
    public constant returns (bool);
функция operatorSend(адрес from, адрес to, сумма uint256,
                      bytes userData,bytes operatorData) public;

событие Отправлено(адрес, индексированный оператором, адрес, индексированный из,
           адрес, на который индексируется, uint256 количество, байты userData,
           байты operatorData);
событие Minted(адрес, индексируемый оператором, адрес, индексируемый к,
             uint256 amount, bytes operatorData);
событие Burned(адрес indexed operator, адрес indexed from,
             uint256 amount, bytes userData, bytes operatorData);
событие AuthorizedOperator(address indexed operator,
                         адрес индексированного токенхолдера);
событие RevokedOperator(оператор с индексом адреса, токенхолдер с индексом адреса);

} крючки ERC777 Спецификация крюка отправителя токенов ERC777: интерфейс ERC777TokensSender { функция tokensToSend(address operator, address from, address to, uint value, bytes userData, bytes operatorData) public; } Реализация этого интерфейса требуется для любого адреса, желающего получать уведомления, обрабатывать или предотвращать списание токенов. Адрес, для которого контракт реализует этот интерфейс, должен быть зарегистрирован через ERC820, независимо от того, реализует ли контракт этот интерфейс для себя или для другого адреса. Спецификация крюка для получателей токенов ERC777: интерфейс ERC777TokensRecipient { function tokensReceived( оператор адреса, адрес от, адрес к, uint сумма, байты userData, байты operatorData ) общественный; } Реализация этого интерфейса требуется для любого адреса, желающего получить уведомление, обработать или отклонить получение токенов. К интерфейсу получателя токенов применяются те же логика и требования, что и к интерфейсу отправителя токенов, с дополнительным ограничением, что контракты получателя должны реализовать этот интерфейс для предотвращения блокировки токенов. Если контракт получателя не зарегистрирует адрес, реализующий этот интерфейс, передача токенов будет неудачной. Важным аспектом является то, что на один адрес может быть зарегистрирован только один отправитель токенов и один получатель токенов. Следовательно, для каждого перевода токенов ERC777 при списании и получении каждого перевода токенов ERC777 вызываются одни и те же функции hook. Конкретный токен может быть идентифицирован в этих функциях с помощью отправителя сообщения, который является конкретным адресом контракта токена, для обработки конкретного случая использования. С другой стороны, одни и те же крючки отправителя и получателя токенов могут быть зарегистрированы для нескольких адресов, и крючки могут различать, кто является отправителем и получателем, используя параметры from и to. Эталонная реализация ERC777 связана с этим предложением. ERC777 зависит от параллельного предложения для контракта реестра, указанного в ERC820. Некоторые дебаты по ERC777 касаются сложности принятия сразу двух больших изменений: нового стандарта токенов и стандарта реестра. Дискуссия продолжается. ERC721: Стандарт нелетучих токенов (Deed) Все стандарты токенов, которые мы рассмотрели до сих пор, предназначены для взаимозаменяемых токенов, то есть единицы токена взаимозаменяемы. Стандарт токенов ERC20 отслеживает только конечный баланс каждого счета и не отслеживает (в явном виде) происхождение любого токена. Предложение ERC721 касается стандарта для неиграбельных токенов, также известных как deeds. Из Оксфордского словаря: акт: Юридический документ, который подписывается и передается, особенно документ, касающийся владения имуществом или юридических прав. Использование слова "deed" призвано отразить часть "владение собственностью", хотя эти документы пока не признаются "юридическими документами" ни в одной юрисдикции. Вполне вероятно, что в какой-то момент в будущем право собственности, основанное на цифровых подписях на платформе блокчейн, будет юридически признано. Нелетучие токены отслеживают владение уникальной вещью. Вещь может быть цифровым предметом, например, внутриигровым предметом или цифровым коллекционным предметом, или физическим предметом, право собственности на который отслеживается с помощью жетона, например, дом, автомобиль или произведение искусства. Сделки могут также представлять вещи с отрицательной стоимостью, такие как кредиты (долги), залоги, сервитуты и т.д. Стандарт ERC721 не накладывает никаких ограничений или ожиданий на природу вещи, право собственности на которую отслеживается с помощью акта, и требует только, чтобы она могла быть однозначно идентифицирована, что в случае данного стандарта достигается с помощью 256-битного идентификатора. Детали стандарта и обсуждение отслеживаются в двух разных местах GitHub: Первоначальное предложение Продолжение обсуждения Чтобы понять основное различие между ERC20 и ERC721, достаточно рассмотреть внутреннюю структуру данных, используемую в ERC721: // Сопоставление идентификатора договора с владельцем mapping (uint256 => адрес) private deedOwner; В то время как ERC20 отслеживает балансы, принадлежащие каждому владельцу, причем владелец является первичным ключом отображения, ERC721 отслеживает ID каждого дела и то, кто им владеет, причем ID дела является первичным ключом отображения. Из этого основного различия вытекают все свойства неиграбельного токена. Спецификация интерфейса контракта ERC721 такова: интерфейс ERC721 /* является ERC165 */ { событие Transfer(address indexed _from, address indexed _to, uint256 _deedId); событие Approval(адрес с индексом _owner, адрес с индексом _approved, uint256 _deedId); событие ApprovalForAll(адрес с индексом _owner, адрес с индексом _operator, bool _approved);

функция balanceOf(address _owner) внешний вид возвращает (uint256 _balance);
функция ownerOf(uint256 _deedId) внешний вид возвращает (адрес _owner);
функция transfer(address _to, uint256 _deedId) внешняя оплачиваемая;
функция transferFrom(адрес _from, адрес _to, uint256 _deedId)
    внешняя кредиторская задолженность;
function approve(address _approved, uint256 _deedId) external payable;
функция setApprovalForAll(address _operator, boolean _approved) payable;
функция supportsInterface(bytes4 interfaceID) внешнего представления возвращает (bool);

} ERC721 также поддерживает два дополнительных интерфейса, один для метаданных, а другой для перечисления актов и владельцев. Необязательный интерфейс ERC721 для метаданных: интерфейс ERC721Metadata /* является ERC721 / { функция name() external pure возвращает (строку _name); функция symbol() external pure возвращает (строку _symbol); function deedUri(uint256 _deedId) внешний вид возвращает (строку _deedUri); } Дополнительный интерфейс ERC721 для перечисления: интерфейс ERC721Enumerable / является ERC721 */ { функция totalSupply() внешнего представления возвращает (uint256 _count); функция deedByIndex(uint256 _index) внешний вид возвращает (uint256 _deedId); функция countOfOwners() внешнего представления возвращает (uint256 _count); функция ownerByIndex(uint256 _index) внешний вид возвращает (адрес _owner); функция deedOfOwnerByIndex(адрес _owner, uint256 _index) внешний вид возвращает (uint256 _deedId); } Использование стандартов токенов В предыдущем разделе мы рассмотрели несколько предложенных стандартов и пару широко внедренных стандартов для токен-контрактов. Что именно делают эти стандарты? Должны ли вы использовать эти стандарты? Как вы должны их использовать? Следует ли вам добавить функциональность, выходящую за рамки этих стандартов? Какие стандарты следует использовать? Далее мы рассмотрим некоторые из этих вопросов. Что такое стандарты токенов? Каково их назначение? Стандарты токенов - это минимальные спецификации для реализации. Это означает, что для того, чтобы соответствовать, скажем, стандарту ERC20, вам необходимо, как минимум, реализовать функции и поведение, указанные в стандарте ERC20. Вы также можете расширить функциональность, реализовав функции, которые не входят в стандарт. Основная цель этих стандартов - стимулировать взаимодействие между контрактами. Таким образом, все кошельки, биржи, пользовательские интерфейсы и другие компоненты инфраструктуры могут взаимодействовать предсказуемым образом с любым контрактом, который следует спецификации. Другими словами, если вы развернете контракт, соответствующий стандарту ERC20, все существующие пользователи кошельков смогут без проблем начать торговлю вашим токеном без какого-либо обновления кошелька или усилий с вашей стороны. Стандарты носят описательный, а не предписывающий характер. Как вы решите реализовать эти функции, зависит от вас - внутреннее функционирование контракта не имеет отношения к стандарту. В них есть некоторые функциональные требования, которые определяют поведение при определенных обстоятельствах, но они не предписывают реализацию. Примером может служить поведение передаточной функции, если ее значение установлено в ноль. Должны ли вы использовать эти стандарты? Учитывая все эти стандарты, каждый разработчик сталкивается с дилеммой: использовать существующие стандарты или внедрять инновации, выходящие за рамки налагаемых ими ограничений? Эту дилемму нелегко разрешить. Стандарты неизбежно ограничивают вашу способность к инновациям, создавая узкую "колею", которой вы должны следовать. С другой стороны, основные стандарты возникли на основе опыта работы с сотнями приложений и часто хорошо подходят для подавляющего большинства случаев использования. Частью этого рассмотрения является еще более важный вопрос: ценность функциональной совместимости и широкого внедрения. Если вы решите использовать существующий стандарт, вы получите ценность всех систем, разработанных для работы с этим стандартом. Если вы решите отойти от стандарта, вам придется учитывать стоимость создания всей инфраструктуры поддержки самостоятельно или убедить других поддержать вашу реализацию в качестве нового стандарта. Тенденция прокладывать свой собственный путь и игнорировать существующие стандарты известна как синдром "здесь ничего не изобрели" и является противоположной культуре открытого исходного кода. С другой стороны, прогресс и инновации зависят от того, чтобы иногда отступать от традиций. Это непростой выбор, поэтому тщательно его обдумайте! Примечание: Согласно Википедии, "Изобретено не здесь" - это позиция, принятая в социальных, корпоративных или институциональных культурах, которые избегают использования или покупки уже существующих продуктов, исследований, стандартов или знаний из-за их внешнего происхождения и затрат, таких как роялти.

Безопасность по срокам погашения Помимо выбора стандарта, существует параллельный выбор реализации. Когда вы решаете использовать такой стандарт, как ERC20, вам необходимо решить, как реализовать совместимую конструкцию. Существует ряд существующих "эталонных" реализаций, которые широко используются в экосистеме Ethereum, или вы можете написать свою собственную с нуля. И снова этот выбор представляет собой дилемму, которая может иметь серьезные последствия для безопасности. Существующие реализации "проверены в боях". Хотя невозможно доказать, что они безопасны, многие из них лежат в основе токенов стоимостью в миллионы долларов. Они подвергались атакам, неоднократным и активным. До сих пор не было обнаружено никаких существенных уязвимостей. Написать свой собственный контракт нелегко - существует множество тонких способов взлома. Гораздо безопаснее использовать хорошо проверенную, широко распространенную реализацию. В наших примерах мы использовали реализацию стандарта ERC20 от OpenZeppelin, поскольку эта реализация с самого начала ориентирована на безопасность. Если вы используете существующую реализацию, вы также можете расширить ее. Однако, опять же, будьте осторожны с этим порывом. Сложность - враг безопасности. Каждая добавленная вами строка кода расширяет поверхность атаки вашего контракта и может представлять собой затаившуюся в ожидании уязвимость. Вы можете не заметить проблему до тех пор, пока не добавите в контракт много ценностей и кто-то его не нарушит. Совет: Стандарты и выбор реализации являются важными составляющими общего дизайна безопасного смарт-контракта, но это не единственные соображения. См. раздел [smart_contract_security].

Расширения стандартов интерфейса токенов Стандарты токенов, рассмотренные в этой главе, предоставляют очень минимальный интерфейс с ограниченной функциональностью. Многие проекты создали расширенные реализации для поддержки функций, необходимых для их приложений. Некоторые из этих функций включают: Контроль со стороны владельца Возможность наделения конкретных адресов или наборов адресов (т.е. схемы мультиподписи) специальными возможностями, такими как составление черных списков, белых списков, чеканка, восстановление и т.д. Горящий Возможность намеренно уничтожать ("сжигать") токены, переводя их на неизрасходованный адрес или стирая баланс и уменьшая предложение. Чеканка Возможность пополнять общее предложение токенов по предсказуемой ставке или по "фиату" создателя токена. Краудфандинг Возможность выставить токены на продажу, например, через аукцион, рыночную продажу, обратный аукцион и т.д. Колпачки Возможность устанавливать заранее определенные и неизменные ограничения на общее предложение (противоположность функции "чеканки"). Бэкдоры для восстановления Функции для возврата средств, обратного перевода или демонтажа токена, который может быть активирован указанным адресом или набором адресов. Белые списки Возможность ограничить действия (например, передачу токенов) определенными адресами. Чаще всего используется для предложения токенов "аккредитованным инвесторам" после проверки по правилам различных юрисдикций. Обычно существует механизм обновления белого списка. Черный список Возможность ограничения передачи токенов путем запрета определенных адресов. Обычно имеется функция обновления черного списка. Для многих из этих функций существуют эталонные реализации, например, в библиотеке OpenZeppelin. Некоторые из них специфичны для конкретного случая использования и реализованы только в нескольких токенах. На данный момент не существует общепринятых стандартов для интерфейсов этих функций. Как уже говорилось ранее, решение о расширении стандарта токена дополнительной функциональностью представляет собой компромисс между инновациями/риском и функциональной совместимостью/безопасностью. Токены и ICO Токены стали взрывным явлением в экосистеме Ethereum. Вполне вероятно, что они станут очень важным компонентом всех платформ смарт-контрактов, подобных Ethereum. Тем не менее, не следует путать важность и будущее влияние этих стандартов с одобрением текущих предложений токенов. Как и в любой технологии на ранней стадии, первая волна продуктов и компаний почти все потерпит неудачу, а некоторые потерпят впечатляющий провал. Многие токены, предлагаемые сегодня в Ethereum, являются едва замаскированными мошенничествами, финансовыми пирамидами и денежными кражами. Фокус в том, чтобы отделить долгосрочное видение и влияние этой технологии, которое, вероятно, будет огромным, от краткосрочного пузыря токенов ICO, которые изобилуют мошенничеством. Стандарты токенов и платформа переживут нынешнюю манию токенов, а затем они, вероятно, изменят мир. Выводы Токены - это очень мощная концепция в Ethereum, которая может стать основой многих важных децентрализованных приложений. В этой главе мы рассмотрели различные типы токенов и стандарты токенов, и вы создали свой первый токен и соответствующее приложение. Мы еще раз вернемся к токенам в [decentralized_applications_chap], где вы будете использовать неиграбельный токен в качестве основы для аукционного DApp.