1. Почему вы обязаны это прочитать
Большинству людей в сфере DeFi приходилось сталкиваться с мошенническими токенами, которые выпускаются с целью завлечь
туда как можно больше людей. Давайте рассмотрим, какие методы используют мошенники, чтобы обманывать неопытных пользователей,
и внедрим некоторые правила – как самому не попасться в руки злоумышленника и не спонсировать его на «новую яхту». В данной
инструкции будут рассмотрены различные способы и уловки мошенника, на которые люди попадаются изо дня в день.
Для начала разберем, что такое смарт-контракт. Зачастую злоумышленники используют уловки, так как в код смарт-контракта можно «зашить» пару
строк, которые по определению будут разделять обычные токены от мошеннических.
Что такое смарт-контракт
Смарт-контракт – это своего рода алгоритм определенных действий, интегрированный в код блокчейна.
При соблюдении установленных договоренностей, которые в нем прописаны, выполняется автоматический запуск последовательности.
Поэтому обязательно нужно понимать, что в нем происходит, так как при выполнении той или иной операции
запускается алгоритм действий, который невозможно предотвратить. Было ли у вас такое, что вы купили токены,
но не смогли их продать, либо же при вызове функции «approve» у вас сразу же списывали все средства с кошелька?
Неважно, какой будет ответ на данный вопрос, наша цель – обезопасить себя на будущее, чтобы больше не терять свои средства.
Мы уверены, никто не хочет потерять лишние $, ведь для них можно найти лучшее применение, чем просто выкинуть их на ветер.
Данный метод мошенничества является одним из самых распространенных и будет рассмотрен по ходу инструкции.
Смарт-контракт представляет собой совокупность блоков кода, каждый из которых отвечает за те или иные действия при вызове
различных функций. Например, блок описания интерфейса (interface IERC20), блок описания контракта (contract 0wnable), блок
описания библиотек (library SafeMath) и т. д.
Обращаем ваше внимание, что все примеры будут взяты из первого попавшегося смарт-контракта, поэтому некоторые блоки могут
отличаться в различных монетах/токенах, но они нам и не нужны. Главное – понять суть и дальше с этим работать. Не переживайте,
при внимательном прочтении любой человек сможет понять и впоследствии отфильтровывать скам-токены. Именно для этого написана эта
инструкция – с целью, чтобы каждый человек понял и применял прочитанное на практике!
Следующая вещь, которую нужно понять, – в рамках контракта всегда существует основной контракт (contract BMON is IERC20, 0wnable),
а все остальные контракты являются вспомогательными. Блоки кода, которые мы упоминали ранее, а именно – блок описания интерфейса,
контракта, библиотек – все это является подключаемой частью основного контракта. В основном эти блоки всегда одни и те же.
Если вы будете периодически анализировать код смарт-контракта, то впоследствии сможете сразу определить,
какие блоки в него включены и что в целом от него ждать.
Функции смарт-контракта могут быть вызваны извне (с кошелька любого пользователя или из другого контракта). Они делятся на две группы:
- Вызов функций первой группы не стоит газа и денег и не уходит дальше ближайшей ноды, к которой мы подключены
(пример: BalanceOf, TotalSupply, Allowance и др.). В BSC scan эти функции перечислены во вкладке «READ».
- Вызов функций второй группы превращается в полноценную транзакцию, которая майнится, включается в блок
и результат, которой записывается в блокчейн (пример: Approve, Transfer, TransferFrom и др.). В BSC scan
эти функции перечислены во вкладке «WRITE».
2. Рассмотрим код основного контракта на примере токена BMON
Не тратьте время на понимание каждой строки. Сейчас показаны отрывки из кода для наглядного примера,
что он из себя представляет и какие основные блоки в него входят:
Любой контракт делится на:
- Описание переменных контракта:
- string private constant _name = "Binamon";
- string private constant _symbol = "BMON";
- uint8 private constant _decimals = 18;
- uint256 private _totalSupply = 300 * 10**6 * 10**18;
- address public seedAndPresale;
- mapping(address => uint256) private balances;
- mapping(address => mapping (address => uint256)) private allowed;
- mapping(address => address) private boosterBuyingAllowed;
- // listing restrictions
- uint256 private restrictionLiftTime;
- uint256 private maxRestrictionAmount = 300 * 10**6 * 10**18;
- mapping (address => bool) private isWhitelisted;
- mapping (address => uint256) private lastTx;
- // end restrictions
- Описание различных функций контракта: (в текстовом варианте представлен неполный код. Полный код смотрите на скрине):
- using SafeMath for uint256;
- enum State
- Locked,
- Restricted, // Bot protection for liquidity pool
- Unlocked
- State public state;
- constructor()
- state = State.Locked;
- balances[msg.sender] = _totalSupply;
- emit Transfer(address(0), msg.sender, _totalSupply);
- function name() public pure returns (string memory)
- return _name;
- function symbol() public pure returns (string memory)
- return _symbol;
- function decimals() public pure returns (uint8)
- return _decimals;
- function totalSupply() public override view returns (uint256)
- return _totalSupply;
- function balanceOf(address tokenOwner) public override view
- returns (uint256)
- return balances[tokenOwner]; и т. д.
Описание различных дополнительных инструментов, которые помогают функциям выполнять свою задачу
Сейчас в отдельности рассмотрим несколько кусков из разных блоков кода. Вы спросите, зачем. Это нужно для того,
чтобы иметь общее и минимальное представление о коде. Таким образом, в будущем вы не будете теряться в новых контрактах,
а сразу сможете находить нужную информацию.
3. Описание переменных
Тип переменной – он всегда идет первым словом. Здесь все довольно просто. Нужно понимать, что все это находится
в блоке описания переменных:
- string private constant _name = “Binamon”;
- string private constant _symbol = “BMON”;
- uint8 private constant _decimals = 18;
- uint256 private _totalSupply = 300 * 10**6 * 10**18;
- address public seedAndPresale;
- mapping(address => uint256) private balances;
- mapping(address => mapping (address => uint256)) private allowed;
- mapping(address => address) private boosterBuyingAllowed;
Пояснения:
- address – это адрес сети блокчейна. Т. е. это либо адрес контракта, либо адрес какого-то кошелька;
- mapping – конструкция mapping представляет из себя хэш-таблицу, в которой ключом является адрес (address => uint256).
Как видим, название у этой переменной – balances. Т. е. мы можем понять, что в этой переменной balances хранится
таблица внутренних держателей токенов.
Таким образом, строчка «mapping(address => uint256) private balances» каждому адресу ставит в соответствие одно и только одно беззнаковое большое целое число, которое является балансом токена. И это все находится в переменной balances.
Важно! Разобравшись с основными конструкциями, стоит подчеркнуть важную особенность. Представленные ниже строки в том или ином виде существуют в каждом смарт-контракте:
- string private constant _name = “Binamon”;
- string private constant _symbol = “BMON”;
- uint8 private constant _decimals = 18;
- uint256 private _totalSupply = 300 * 10**6 * 10**18.
Эти строки задают основные параметры смарт-контракта:
- Имя токена: Binamon.
- Символ токена: BMON.
- Количество десятичных знаков токена: 18.
- Общая эмиссия токена: 300 * 10**6 * 10**18.
Аналогично в том или ином виде вы увидите следующие строки в каждом смарт-контракте:
Строка mapping(address => uint256) private balances – отображает балансы всех адресов, которые хранят свои токены.
Строка mapping(address => mapping (address => uint256)) private allowed – нужна для того, чтобы можно было запустить
TransferFrom от имени того же Pancake. Это необходимо, чтобы заапрувить этот адрес. Таким образом, он будет иметь возможность
снимать деньги с вашего кошелька.
Итак, каждая переменная в контракте имеет свой тип, имеет свой модификатор (не обозревали его в данном блоке,
это – public и private) и значение.
Описание функций
Перейдем к описанию функций. Нельзя сказать, что функции являются ключевой частью контракта,
но в совокупности этот блок является самым важным.
Для начала отметим, что функции могут иметь модификаторы public и private. В предыдущем блоке мы намеренно не стали поднимать данный вопрос,
чтобы пояснить его в блоке описания функций. Итак, если функция имеет модификатор private, то она будет видна только внутри контракта.
Если функция имеет модификатор public, то, соответственно, данная функция видна снаружи, и мы можем ее увидеть. Точно такая же аналогия
будет и в блоке описания переменных.
Как мы уже говорили ранее, все функции в контракте делятся на две большие группы:
- Первая группа – те, которые НЕ меняют состояние блокчейна, а только читают из текущего состояния, и запрос на эту функцию в контракте не уходит дальше ближайшей ноды – эти функции видны у нас во вкладке «READ».
- Вторая группа – функции, которые проходят через нормальную цепочку майнинга, как стандартная транзакция, включаются в блок, в цепочку – мы за это платим газ и т. п. Именно эти функции, которые требуют транзакции, если они описаны как public, то они у нас видны во вкладке «WRITE» в блокчейне.
В итоге:
- Функция READ – это все функции и переменные, которые мы можем прочитать из контракта.
- Функция WRITE – это те рычаги управления контрактом, с помощью которых можно управлять значением переменных и которые мы можем использовать для себя (позже рассмотрим, в каких ситуациях). По стандарту ERC-20, на котором построены все токены, у каждого из них должны быть три основные WRITE-функции: transfer, transferFrom, approve. Тем самым, во вкладке WRITE вы увидите как минимум эти три функции у стандартного токена, в ином же случае этот токен не был бы токеном.
Помимо стандартных функций разработчик может добавить другие функции, с помощью которых он может сам управлять поведением контракта.
Давайте рассмотрим наиболее удачный вариант кода функции Transfer:
- function transfer(address receiver, uint256 numTokens) public override launchRestrict(msg.sender, receiver, numTokens) returns (bool) {
- require(numTokens > 0, "Transfer amount must be greater than zero");
- require(numTokens <= balances[msg.sender]);
- balances[msg.sender] = balances[msg.sender].sub(numTokens);
- balances[receiver] = balances[receiver].add(numTokens);
- emit Transfer(msg.sender, receiver, numTokens);
- return true;
Здесь нет никаких black- и white-листов, подводных камней, что очень любят использовать мошенники в разных
смарт-контрактах. Чуть позже мы рассмотрим такие примеры.
Сейчас давайте опишем, что значит каждая строка в функции Transfer:
- require(numTokens > 0, "Transfer amount must be greater than zero");
Данная строка проверяет, что количество токенов, которое мы хотим передать, – больше нуля. - require(numTokens <= balances[msg.sender]);
Здесь проверяется условие того, что количество токенов – меньше или равно балансу при отправке. - balances[msg.sender] = balances[msg.sender].sub(numTokens);
- balances[receiver] = balances[receiver].add(numTokens);
Переменная balances [msg.sender] уменьшает количество токенов, когда мы их отправили.
Переменная balances [receiver] увеличивает количество токенов, т. е. просто меняется местами значение в двух ячейках.
Когда мы производим transfer(перевод) токенов, то таким действием мы просто меняем значение в ячейках.
И пример кода, который приведен выше, является отличным примером чистого кода функции transfer.
Единственное, стоило бы добавить здесь поподробнее про 2-ю и 3-ю строки функции transfer.
- require(numTokens > 0, "Transfer amount must be greater than zero");
- require (numTokens <= balances[msg.sender]);
Это некая инструкция, которая проверяет значение в первой части условия и, если оно не выполняется, то блокирует
транзакцию и выдает ошибку «Transfer amount must be greater than zero». Если же количество токенов больше 0, то
транзакция проходит дальше по всем строкам.
Во втором случае аналогия такая же: require (numTokens <= balances[msg.sender]).
Требуется, чтобы количество токенов было меньше или равно балансу того, кто хочет отправить.
Директива require будет встречаться очень часто в разных смарт-контрактах. Обычно именно require
и стоит на том самом месте, на котором скам-монета проверяет какое-то условие, в котором вам не
разрешают что-либо сделать. В нашем же случае код полностью легитимный. Также у require есть
брат-близнец – assert. По сути, assert выполняет те же самые функции, что и require.
Это пояснение нужно для нашего анализа. Поехали дальше!
На последнюю строчку – emit Transfer(msg.sender, receiver, numTokens) – не обращайте внимания.
По сути, это выдача в блокчейн некого сигнала о том, что операция проведена. В рамках нашего анализа она не нужна.
Перейдем к функции approve:
- function approve(address delegate, uint256 numTokens) public override returns (bool) {
- allowed[msg.sender][delegate] = numTokens;
- emit Approval(msg.sender, delegate, numTokens);
- return true;
Здесь представлена самая чистая функция approve, которая может быть. Кто не знает, функция approve позволяет адресу
delegate делать транзакции на списание определенного количества токенов с адреса msg.sender, т. е. того,
кто вызвал эту функцию.
Рассмотрим в общем каждую строку, чтобы понимать, за что она отвечает:
- allowed[msg.sender][delegate] = numTokens;
Данная строка меняет состояние таблицы allowed, в которой по адресу msg.sender, т. е. того, кто отправил,
по адресу delegate – кому мы отправляем, даем разрешение на определенное количество токенов numTokens.
TransferFrom:
- function transferFrom(address owner, address receiver, uint256 numTokens) public override launchRestrict(owner, receiver, numTokens) returns (bool) {
- require(numTokens <= balances[owner]);
- require(boosterBuyingAllowed[owner] == msg.sender || numTokens <= allowed[owner][msg.sender]);
- balances[owner] = balances[owner].sub(numTokens);
- if (boosterBuyingAllowed[owner] != msg.sender) {
- allowed[owner][msg.sender] = allowed[owner][msg.sender].sub(numTokens);
- balances[receiver] = balances[receiver].add(numTokens);
- emit Transfer(owner, receiver, numTokens);
- } else {
- _totalSupply = _totalSupply.sub(numTokens);
- emit Transfer(owner, address(0), numTokens);
- }
- return true;
- }
Небольшими шагами добрались до третьей и крайней ключевой функции в нашем анализе – TransferFrom.
Представлена она также в чистом виде. Как говорится, все по фэншую. Единственное, что здесь есть, – бустеры.
Не будем даже понимать, что такое бустеры, а сразу перейдем к подведению итога и к разбору на практике!
Итак, давайте подведем итог. Основные блоки, которые мы будем просматривать, – это transfer, transferFrom и approve.
Это основные вещи, которым нужно уделять особое внимание для быстрого анализа контракта, если там есть что-то
нездоровое или какой-то скам. Обычно именно в этих функциях мошенники внедряют различную черноту. Стоит отметить,
что в большинстве своем, смотря на данные функции, вы не увидите сразу полного кода. Там будет
что-то по типу _approve, _transfer, _transferFrom. Зачастую программисты ленивы и сводят похожие части кода в
один блок, чтобы потом вставлять его в различные функции. Поэтому мы обязательно рассмотрим такие примеры и,
если вы увидите, что в каком-то из этих трех блоков чего-то не хватает, но там есть похожие строки кода,
то нужно будет найти, где прописана сама функция. На примерах все станет понятнее. Обязательно сконцентрируемся
на этом моменте, как дойдем до него.
Помимо тех функций, которые мы рассмотрели и которые присутствуют в стандарте ERC-20 и являются необходимыми,
чтобы токен был токеном, существует различное множество других функций, блоков, кода и т. п. Но не стоит пугаться!
Разобрав единожды хотя бы один контракт, вы уже сможете выделять мошеннические смарт-контракты и отсекать их,
так как вы будете это знать!
При анализе смарт-контрактов сразу отмечайте, является ли представленный код открытым. Потому что многие скам-контракты
не имеют исходного кода, а содержат только бинарный код. Это значит, что создатель по тем или иным причинам не хочет,
чтобы его контракт был верифицированным. По каким причинам – непонятно, но, к примеру, если это скам, то создатель не
хочет, чтобы кто-то проанализировал его смарт-контракт и выявил мошеннические строки.
Также давайте немного отвлечемся и рассмотрим, что происходит, когда какой-то разработчик берет контракт, который
он уже сделал, и хочет поместить его в блокчейн. Если не вдаваться в детали, то контракт в виде исходного кода,
скомпилированного в байткод, записывается в блокчейн, после этого выполняется один раз функция конструктор (constructor),
которая записана в коде контракта:
- constructor() {
- state = State.Locked;
- balances[msg.sender] = _totalSupply;
- emit Transfer(address(0), msg.sender, _totalSupply);
После этого контракт разворачивается уже на исполнение. Если разработчик хочет,
чтобы его контракт был верифицированным, он может залить свой исходный код.
Некоторые из контрактов имеют только байткод. Это значит, что разработчик не хочет выкладывать в открытый
доступ исходный код. Поэтому это один из триггеров, что, вероятнее всего, это ханипот.
Конструктор – это функция, которая выполняется один раз. В ней зачастую происходит инициализация переменных.
Разбор скам-токенов
Итак, часть основных моментов мы разобрали. Теперь перейдем к сути. Те токены и монеты, которые требуют изучения,
кода, зачастую называются ханипотом (honeypot).
Давайте разберем несколько ханипотов и проанализируем их код. Перед началом сделаем важную ремарку.
Некоторые токены закрывают доступ к открытому исходному коду, в связи с чем в блокчейне мы можем увидеть
только их ByteCode. Всегда задумывайтесь и тщательно проверяйте данные токены, если они вам действительно
интересны по своим причинам. Пример таких токенов с закрытым исходным кодом:
Ниже мы рассмотрим пример и такого токена, который можно постараться проанализировать без открытых исходников:
1. DogeUnicorn (DGU)
Данный контракт токена имеет открытый исходный код, но это не подтверждает его легитимность.
Давайте по пунктам рассмотрим, как правильно проводить анализ:
- Переходим во вкладку «READ» в обозревателе блокчейна и смотрим, какие у нас имеются открытые переменные:


Ремарка: не старайтесь быть перфекционистом и пытаться сразу все понять с ходу. Оценивайте для начала в общем,
что из себя представляет данный контракт и что он умеет, а потом уже углубляйтесь.
После того, как мы в целом ознакомились с информацией во вкладке «READ», можно сделать вывод, что внутри есть
как минимум некое сжигание токенов, дивидендная разбивка холдерам и некий таймаут на сбор наград холдерам.
Оценили и переходим во вкладку «WRITE».
- Переходим во вкладку «WRITE» и по такому же принципу анализируем информацию.


После краткого ознакомления с функциями во вкладках «WRITE» и «READ» переходим непосредственно в код контракта. Сейчас мы рассматриваем именно ту ситуацию, когда код контракта выложен в публичный доступ.
- Переходим в код смарт-контракта.
Первым делом схлопываем его, чтобы найти базовый контракт. Делаем это при нажатии на стрелочки:

Получаем в итоге данный вид:
Рассмотрим блоки transfer и transferFrom:


Возвращаемся как раз к теме про ленивых программистов. Видим, что в функции transfer и TransferFrom находится одна и та же строка,
которая начинается с «_transfer». В данном случае мы должны найти функцию _transfer, чтобы непосредственно ее проанализировать.
Найдя данную функцию, видим, что здесь:

В данном контракте имеются рычаги управления, с помощью которых создатель может запретить продажу токенов.
Это один из сигналов задуматься, стоит ли покупать этот токен.
2. Snoopy Inu (SNPINU)
С данным токеном все проделываем аналогично, но заранее скажу, что в нем скам зашит в функции approve.
Опять же, видим включенную функцию _approve в нашей текущей. Ищем ее.
Вот он скам! Если owner не является определенным адресом, то и продать мы НЕ можем!
3. Еще пример – MiniDOGE.
Рассмотрим вариант, когда код токена не выложен в публичный доступ.
Мы также можем постараться найти скам в этом случае через байткод, но зачастую это уже должно быть для
вас триггером в покупке этого токена.
После декомпиляции и преобразования кода в более-менее читаемый вид просматриваем его, как обычно:
точно такой же алгоритм, который был разобран в самом начале. Стараемся найти в данном байткоде transfer, transferFrom и approve.
Самое интересное заключается в подчеркнутой строке (if 0x283f144a8177175b06dcf6323ffad3e68f1c5a61 != caller)! Если тот,
кто вызвал этот контракт, не является владельцем этого адреса, тогда равенство не выполняется и нам говорят: «Нет, друг,
продать ты ничего не сможешь… eсли только не подберешь сид-фразу к моему кошельку со скам-токенами».
В итоге мы разобрали пример анализа ханипот-монет на наличие скама. Существуют также и другие виды мошенничества.
Общий свод будет представлен ниже.
Рагпул, ханипот и другие скам-токены
- Рагпул – токены, в которых исчезает вся ликвидность, и вы остаетесь с кучей шлака, который невозможно продать.
В целом такие токены можно распознать, вбив их на любом сайте проверки на скам. Данный вид скама связан с хранением большого числа токенов на одном или нескольких кошельках, чтобы в один момент либо все слить по рынку, либо, если это связано с ликвидностью, то разобрать LP-токены и вытащить всю ликвидность. Через контракт данный вид скама распознать нельзя – только путем изучения холдеров и самого проекта. - Ханипот – контракт, в коде которого прописана невозможность продажи токенов. Купить можно, а продать – нет.
Мы разобрали, как можно постараться обезопасить себя от таких токенов, изучив определенные блоки в контракте, либо отсеяв их путем того, что у данного токена нет открытого исходного кода. Зачастую такие токены оказываются скамом. - Другие скам-токены. Мир криптовалют постоянно меняется, и учесть все мельчайшие факторы просто невозможно. Поэтому существуют и другие виды скам-токенов, связанные с ограничением продажи, таймаутом транзакций и т. д. Но самый большой круг скама – ханипот – мы захватили, поэтому вероятность наткнуться на другие скам-токены, которые вы + сходу не сможете распознать, – небольшая.
Данная большая статья-инструкция дает понимание того, что если человек действительно хочет быть профессионалом в мире криптовалют, то помимо умения нажимать кнопки approve, buy, sell, нужно уметь разбираться в коде контракта и знать хотя бы базовые вещи по программированию. Такие знания можно усвоить при должном усилии за небольшой промежуток времени – именно знания, которые помогут между строчек прочитать однотипные контракты и задаться вопросом в том или ином виде, действительно ли код является легитимным. Всем, кто смог дойти до самого конца, желаем успешных покорений новых вершин криптовалютного мира. Вы большие молодцы!