- BrainTools - https://www.braintools.ru -

Как одеть гидру в броню или взлом смарт-контрактов на DeFi-хаке

Философия, мысли и спойлеры

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

В общем, мы стараемся на зацикливаться на конкретно одной тематике и/или специфике, и постоянно пробуем себя, в различных направлениях. Да, не скрою, порой мы ввязываемся в, откровенно, рискованные истории, там, где совсем не имеем опыта [1] и/или имеем крайне отдаленное представление о том, что от нас требуется ;)). Плохо это или хорошо, тут можно мыслить двояко, однако, у нас получается, учитывая, что из 10 хакатонов, которые мы провели 8 оказались, для нас успешными (или почти) и мы весьма преуспели на этом поприще соревновательного Data Science.

При этом, выходя на хак, мы для себя уже составили небольшой роадмап, что нам, в целом, интересно, помимо решения предложенного кейса и на что мы акцентируем внимание [2] в первую очередь:

  • Во-первых, мы постоянно “щупаем” рынок и смотрим, что сейчас востребовано, на что направленны усилия бизнеса, что ему интересно и какие, в этой связи, наработки можем предложить мы исходя из наших возможностей и познаний;

  • Во-вторых, только самые нестандартные решения и задачи на “стыке”, и я бы даже сказал, на конфликте [3] интересов, дают совершенно уникальные прорывы. Так было у нас часто, когда для определения эпилепсии (на одном из хакатоне) мы использовали высокочастотные алгоритмы из трейдинга и получили точность в 97%, или, когда мы применяли решения из теории графов к задачам информационной безопасности. Во многом это дает развитие новых подходов и получается посмотреть на задачу с иного угла, а не заниматься бесконечной оптимизацией наработанных уже существующих технологий;

  • В-третьих, мы всегда “мониторим” стек передовых разработок, тем самым оперативно пополняем свой багаж новыми идеями и анализом решений конкурентов, будь то подходы, модели, методики и/или практики кодинга. Сюда же входят стратегии, анализ и идейная составляющая заявленных кейсов. Тут идет вход буквально все от обширного поиска идей и решений по всем имеющимся ресурсам до рассмотрения самых бредовых и невероятных идей;

  • В-четвертых, это проверка своих сил, и понимания неких политических процессов и веяний от текущих экономических реалий, в которые мы все погружены и должны учитывать при корректировки своих научных задач, потребностей [4], направлений разработки и экспертной мысли. Во многом, это позволяет понять куда “дует” ветер и что надо заказчику, что у него в голове, о чем он думает и самое главное, во что он готов инвестировать.

  • В-пятых, это разработка собственных внутренних продуктов, расширение деловых связей, обкатка “домашних заготовок”, тактик, подходов и анализа работы готовых инструментов, оттачивания сложившихся настроений и взглядов на разработку ПО.  

Как видите, задачи и решения весьма и весьма разноплановые, но в общем, укладываются в единую канву и специфику спортивного Data Science, алгоритмизации, кодинга, математики [5] и философии программирования в целом.

Примерно, с такими же настроениями, мы пришли и на этот (DeFi-2025 [6], Сбер) очередной хак, посвященный разработке блокчейн-решений и анализу уязвимостей смарт-контрактов и web-3.

Забегая вперед скажу, что нам не хватило, каких-то 0.06% для того, чтобы войти в топ-8 номинируемых команд. В общем, мы получили по сумме баллов за наше решение 13.44, тогда как у лидера было порядка 16. Отрыв небольшой, учитывая сложность задачи и ее первоначальное очень жесткое условие: одним из проходных критериев было создать уникальное решение, до этого нигде не фигурирующее в цифровом поле до 2025 года текущего месяца. С таким мы столкнулись впервые и были немного удивлены. Да. хакатон и подразумевает нечто подобное, но все же надо было понять, что вместе с нами было еще 28 команд, и они тоже умеют гуглить ;)). Наша команда называлась BlockTeam.

 

Первый раунд оценки решений

Первый раунд оценки решений

Организация, команда и все-все-все

Итак, полетели карусели ;)). Всего на хак было выделено 3 недели: анализ, запилы, допилы, перепилы и снова перетяжка синей изолентой со всеми костылями, ночными созвонами, случайными волшебными озарениями, паникой на борту и литрами поглощённого кофе. Скажу сразу, мне понравилось, и команда тоже откровенно кайфанула, а как должно быть по-другому на соревнованиях ;)). Говоря о команде, мы зашли на хак с “ноги” втроем и как-то быстро раскинули задачи. Прикинули, что за чем идет и кто что делает, при том, что бодрая коммуникация созрела в головах как-то сама собой. Более того, к нам присоединился человек и другого вуза и получилось сборная Red-Team по анализу смарт-контрактов:

  • Владимир Миронов (это я) (Финансовый университет при Правительстве РФ): аналитика, проработка общего решения, математика, тестирование гипотез и уязвимостей смарт-контрактов, конвертация смарт-контрактов в графы, файн-тюнинг моделей машинного обучения [7], общая мотивация [8] и задор и научный [9] руководитель по курсовой Александра Лобова;

  • Александр Лобов (Финансовый университет при Правительстве РФ): аналитика, анализ работы смарт-контрактов, работа с инструментами по взлому смарт-контрактов, поиск багов, бэкенд, разработка моделей машинного обучения, чил на уровне серфера и запиленный за неделю курсач, который, кстати лег в базу этого хака;

  • Санников Николай (МГТУ им. Баумана): аналитика, анализ работы смарт-контрактов, разработка сайта, поднятие чат-бота, фронтенд, DevOps, дизайн, ресерч, тайминг вкупе с дожиманием идеи и неиссякаемый юмор [10].

Теперь кейсы. Нам на выбор предложили 6 раскаток, в общем и целом, одного ковша разлива, если подходить максимально широко и открыто, но это и хорошо. Нам не пришлось долго выбирать и терзаться сомнениями, что и как, а то или не то, и мы сразу “погнали” в бой. Да, надо отдать должное организаторам и перечислить все-таки все таски какие они были в дикой природе, так вот:

  • Сервис для защиты персональной информации;

  • Протокол восстанавливаемых биометрических ключей;

  • Сервис по аналитике и мониторингу смарт-контрактов и отслеживанию изменений в них (наш);

  • ZkKYC — сервис, который позволит упростить пользовательский опыт в части KYC;

  • Платформа оракулов;

По таймингу было 26 марта – 12 мая – сам период проведения хакатона,12 мая – дедлайн подачи решений; 30 мая – публичные питчи финалистов на митапе. Дополнительно нам еще обозначили, что надо сделать поддержку сети TRON (это был отдельный челендж с блекджеком и анализом красоты, но об этом чуть позже). Кроме всего, нам выкатили требования, что хотелось бы увидеть и более того сделали акцент, что, если кто-то сделает решение задачи на графах, это будет, прям самый самолет. И мы такое сделали)))

Таймлайн по хакатону

Таймлайн по хакатону
Требования по реализации

Требования по реализации

Разработка, тестирование и суровая математика

Мы для себя взял задачку по мониторингу смарт-контрактов и более того, она была один в один похожа на тему курсовой Александра (“Моделирование и анализ поведения [11] смарт-контрактов на блокчейнах с использованием ML”), которую я предложил Саше в нашем универе. Разработали концепт, идею, вектор движения и направленности, быстро обсудили, прикинули тулзы и история “покатилась”. Саша “мухой” написал, работу, отлично защитил ее, показал уверенное знание предмета и что не маловажно, мы получили качественно-полное решение с моделью машинного обучения и анализом.

Однако, для хака этого было мало, и мы “завернули” наше решение в кое-что помоднее. Мы стали думать, как подойти к анализу уязвимостей более полно. Наша задача состояла в разработке анализатора уязвимостей для смарт-контрактов и оценке того, как наиболее полно можно проводить тестирование, а главное загодя. При этом, мы предложили “перегонять” смарт-контракты в графы, далее исследуя топологию графов проводить оценку структуры и выявлять характерные паттерны. Более того, подобные паттерны мы сделали и для CVE (уязвимости смарт-контрактов). Сравнивая механизмы и сигнатуры смартов и CVE, мы можем анализировать и на “горячую”, говорить о том является ли смарт-контракт уязвимым или нет.

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

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

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

Итак, при построении итогового графа мы ориентировались еще и на метрики для анализа их геометрии, коих придумали порядка 28. Список, конечно, является не полным, и мы постоянно его расширяем и дополняем (да продолжаем проект, уже в рамках более расширенного класса задач), но все же решили выкатить:

  • Минимальное расстояние между вершинами;

  • Средняя длина ребра (Average rib length, ARL);

  • Дисперсия длин рёбер (Dispersion of edge lengths, DEL);

  • Максимальное отклонение углов (Maximum angle deviation, MAD);

  • Пересечения рёбер (Rib crossings, RC);

  • Площадь покрытия (Coverage area, CA);

  • Коэффициент сохранения соседних вершин (Conservation coefficient of neighboring vertices, CNF);

  • Степень соблюдения слоев (Degree of adherence to layers, DAL);

  • Расстояние центров масс компонентов (Distance of centers of mass of components, DCMC);

  • Симметрия [12] (Symmetry, Sym);

  • Регулярность формы (Regularity of form, RF);

  • Разделение уровня важности (Separation of the level of importance, SLI);

  • Соотношение сторон окна (Window aspect ratio, WR);

  • Огибающая линия контура графа (Graph Contour Envelope);

  • Среднее абсолютное отклонение длины рёбер (Mean Absolute Edge Length Deviation);

  • Угол поворота (Angle Preservation Metric);

  • Критерий корреляции взаиморасположения (Proximity Correlation Criterion, PCC);

  • Показатель осевого выравнивания (Axis Alignment Index, AAI);

  • Суммарная кривизна рёбер (Total Edge Curvature, TEC);

  • Индекс стохастичности ориентации (Stochastic Orientation Index, SOI);

  • Нормированная вариация площади ячейки (Normalized Cell Area Variation, NCAV);

  • Относительная разница между максимальным и средним расстоянием (Relative    Distance Difference Ratio, RDDR);

  • Размер краевых выпуклых оболочек (Convex Hull Size of Edges, CHSE);

Следующим моментом было всесторонне обнаружение уязвимости, и был собран конвейер на основе smartBugs и Slither (статические анализаторы смарт-контрактов). В качестве каркаса мы взяли smartBugs (куда уже входят smartCheck, solhint и conkas), но столкнулись с тем, что встроенный Slither работал нестабильно. Поэтому отдельно дополнили smartBugs более свежей версией Slither. Для удобства написали скрипт, который по адресу контракта на Etherscan (или в нужной сети) подтягивали исходники, строили дерево импортов и прогонял анализаторы. Результат каждого инструмента сохранялся в CSV вида:

Описание csv файла

Описание csv файла
Как одеть гидру в броню или взлом смарт-контрактов на DeFi-хаке - 5

После smartBugs мы запускали Slither — и расширяли таблицу до ~144 признаков, каждый из которых указывал на потенциальную проблему. Эти фичи кормили ансамбли машинного обучения: GradientBoosting, XGBoost, LightGBM, RandomForest и VotingClassifier. В итоге мы получали предсказания о наличии уязвимостей.

Дополнительно «прогоняли» контракт через свежий Slither: он возвращает конкретные ошибки [13] и номера строк. Мы подтягиваем исходники заново и формировали PDF-отчет, где для каждой проблемы показывали фрагменты кода и его описание – чтобы ревьюер мог мгновенно оценить “тревожные” места.

Здесь мы поддерживали только Ethereum и L2. Были попытки добавить TRON, но они “упёрлись” в отсутствие нормального Tronscan API: парсили страницы, пробовали декодировать байткод – но в условиях хакатона решили не тратить на это драгоценное время. Хотя решение по факту нашли и на эту проблему. Параллельно сделали на сайте фичу подписки на обновление прокси-контрактов. Логика [14] простая:

  1. Пользователь указывает адрес контракта, обновление которого он хочет отследить, мы через Etherscan API получаем реализацию (Implementation);

  2. Сохраняем код реализации и подписываемся на событие Upgraded;

  3. Как только контракт обновляется, ловим новый адрес, подтягиваем его код и шлём уведомление в Telegram с до/после.

Типичное описание уязвимости которое мы получали

Типичное описание уязвимости которое мы получали

В планах — не только сообщать об обновлении, но и показывать diff между версиями и текстовое описание изменений. Ещё можно перестраховаться и несколько раз в сутки дергать реализацию напрямую (если кто-то удалит ивент Upgraded), но это в следующий релиз – нам же нужно было успеть к дедлайну!

Метрики по анализу уязвимостей по смарт-контрактам

Метрики по анализу уязвимостей по смарт-контрактам

Далее, необходимо было все это оформить на платформе и “прикрутить” сервисы для работы. При этом ключевым решением стал выбор микросервисной архитектуры. Этот подход был выбран не случайно. Он позволил:

  1. Использовать лучший инструмент для каждой задачи: Python с его богатой экосистемой для ML и анализа данных был выбран для бэкенда; он же, благодаря зрелым фреймворкам (aiogram), был использован и для Telegram-бота. Node.js, идеально подходящий для асинхронных I/O-операций, лег в основу сервиса мониторинга. React и TypeScript что обеспечило создание сложного и надежного пользовательского интерфейса.

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

  3. Упрощение независимой разработки и масштабирование: Каждый сервис может дорабатываться и масштабироваться отдельно в зависимости от нагрузки. Оркестрация всех компонентов доверена docker-compose, что стандартизирует процесс развертывания и локальной разработки. Стек фронтенда был выбран для достижения максимальной скорости разработки и долгосрочной надежности.

  • React 19: Выбран за проверенный компонентный подход, который упрощает создание пользовательской части приложения, и за огромную экосистему, гарантирующую стабильность.

  • TypeScript: Интегрирован для строгой типизации, что кардинально снижало количество ошибок еще на этапе написания кода и упрощало поддержку проекта.

  • Vite: Применен как сборщик нового поколения для обеспечения практически мгновенной “горячей” загрузки в разработке (HMR) и оптимизации финальной сборки.

Управление данными

TanStack: Чтобы не превращать код в бесконечную проверку статусов “загружается…”, “ошибка!”, “получилось!”, мы использовали TanStack Query. Эту библиотеку можно представить, как личного ассистента для работы с запросами. Разработчик просто говорит: “добудь мне список контрактов”, а TanStack Query сам разбирается со всей рутиной: он запомнит результат (кэширование), будет незаметно для пользователя обновлять его в фоне и не станет делать лишний запрос, если эти же данные понадобятся в другом месте через секунду. В итоге интерфейс работает быстрее и плавнее, а в коде не остается “душнины” из сотен isLoading.

Архитектура FSD

Для долгосрочной поддержки проекта была выбрана архитектура Feature-Sliced Design. Её выбрали за ключевое преимущество: код группируется не по техническим типам, а по бизнес-фичам. Такой подход радикально упрощает навигацию по коду, обеспечивает изоляцию фич друг от друга и позволяет легко их переиспользовать. Это делает разработку предсказуемой и готовой к масштабированию.

Структура приложения

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

Бэкенд: Мозговой и Аналитический Центр

Это ядро всей системы, где происходит основная магия.          

Выбор фреймворка: FastAPI был выбран за его производительность, которая достигается за счет асинхронности на базе asyncio, и за строгую типизацию благодаря Pydantic. Встроенная автоматическая генерация документации по стандарту OpenAPI (широко известному как Swagger).

Безопасность и Аутентификация

Реализован серверный флоу OAuth2 (Open Authorization), что является стандартом безопасности. Фронтенд не хранит и не обрабатывает секреты клиента. Используется двухступенчатая система токенов: Refresh Token: Долгоживущий JWT (JSON Web Token), который безопасно хранится в httpOnly cookie. Этот флаг делает токен недоступным для JavaScript, что защищает его от кражи через вредоносные скрипты, внедренные на страницу.

Access Token: Короткоживущий JWT (JSON Web Token), который хранится в памяти [15] на фронтенде и используется для аутентификации API-запросов. Его короткий срок жизни минимизирует риски в случае его утечки. Этот подход обеспечивает баланс между удобством (пользователю не нужно логиниться каждый 15 минут) и безопасностью.

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

Сервис Мониторинга: Неусыпный Страж

Этот сервис — самая инновационная часть проекта. Обоснование стека: Node.js и TypeScript были выбраны как идеальное решение для задачи, требующей обработки большого количества одновременных, долгоживущих сетевых соединений (подписки на WebSocket RPC). Событийно-ориентированная модель Node.js здесь раскрывается в полной мере, что делает этот стек стандартом для многих DeFi-проектов, работающих с блокчейном в реальном времени.

Механизм работы:

  1. Сервис получает из БД список прокси-контрактов для отслеживания.

  2. Он открывает WebSocket-соединения с RPC-узлами выбранных блокчейн-сетей.

  3. В рамках этих соединений он подписывается на событие Upgraded(address indexed implementation) для каждого из отслеживаемых контрактов. Этот ивент является стандартом для большинства обновляемых прокси (EIP-1967).

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

Эффективность: Использование WebSocket и подписки на события на несколько порядков эффективнее, чем постоянный опрос (polling) состояния каждого контракта.

База Данных: Общее Сердце Системы

В качестве системы управления базами данных был выбран PostgreSQL. Это мощная и надежная реляционная СУБД, которая отлично подходит для хранения структурированных данных о пользователях, контрактах и результатах аудитов. Взаимодействие с бэкендом: Бэкенд работает с БД через высокоуровневые инструменты SQLAlchemy и SQLModel, что позволяет ему оперировать данными как Python-объектами и упрощает реализацию сложной бизнес-логики. Роль в экосистеме: База данных — это не просто хранилище, а связующее звено между всеми сервисами. Она хранит данные пользователей, результаты аудитов, а также служит “доской задач” для сервиса мониторинга.

Telegram-бот: Полноценный Интерфейс в Мессенджере

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

Python был выбран для консистентности с основным бэкендом, что упрощает переиспользование логики и моделей данных. В качестве фреймворка был использован aiogram 3.x — современный и высокопроизводительный асинхронный инструмент, идеально подходящий для создания сложных, отзывчивых ботов.

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

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

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

Ключевая роль — Уведомления: Но главная особенность, ради которой создавался бот, — это

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

Подход к Развертыванию: Классический и Надежный

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

Оркестрация: Управление всеми компонентами экосистемы (Web, Backend, Updates, Bot, DB) осуществляется через docker-compose. Это значительно упрощает процесс развертывания, поскольку вся сложная конфигурация сети и зависимостей между сервисами описывается в одном декларативном файле и управляется единой командой.

Итоговое Заключение: Архитектура для Роста

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

Ключевые сильные стороны архитектуры

Стратегическое разделение ответственности: Выбор микросервисной архитектуры не случаен. Он позволил четко разделить систему на независимые, логические блоки: пользовательский интерфейс (Web), ядро с бизнес-логикой (Backend), инновационный сервис мониторинга (Updates), точка входа через мессенджер (Bot) и общее хранилище (DB). Такое разделение является залогом стабильности и упрощает поддержку.

Обоснованный полиглотный подход: Для каждого сервиса был выбран наиболее подходящий инструмент — React/TS для сложного интерфейса, Python/FastAPI для удобной разработки API и Node.js для высокопроизводительных I/O-операций в сервисе мониторинга. Это прагматичное решение, а не погоня за технологиями. Два полноценных интерфейса: Предоставление пользователю выбора между полнофункциональным веб-приложением и Telegram-ботом, который полностью дублирует его возможности, значительно расширяет охват и удобство использования платформы.

Потенциал для развития

Выбранная архитектура создает прочный фундамент для дальнейшего роста. Изолированность сервисов позволяет безболезненно добавлять новый функционал, например, настройка CI/CD (непрерывная интеграция и доставка): Автоматизация процессов тестирования, сборки Docker-образов и развертывания через GitHub Actions или GitLab CI значительно ускорит и обезопасит процесс вывода новых версий в продуктив. 

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

Как одеть гидру в броню или взлом смарт-контрактов на DeFi-хаке - 8
Как одеть гидру в броню или взлом смарт-контрактов на DeFi-хаке - 9

Автор: seiros

Источник [16]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/19697

URLs in this post:

[1] опыта: http://www.braintools.ru/article/6952

[2] внимание: http://www.braintools.ru/article/7595

[3] конфликте: http://www.braintools.ru/article/7708

[4] потребностей: http://www.braintools.ru/article/9534

[5] математики: http://www.braintools.ru/article/7620

[6] DeFi-2025: https://sberstudent.ru/our_event/defi-hack-2025/

[7] обучения: http://www.braintools.ru/article/5125

[8] мотивация: http://www.braintools.ru/article/9537

[9] научный: http://www.braintools.ru/article/7634

[10] юмор: http://www.braintools.ru/article/3517

[11] поведения: http://www.braintools.ru/article/9372

[12] Симметрия: http://www.braintools.ru/article/3088

[13] ошибки: http://www.braintools.ru/article/4192

[14] Логика: http://www.braintools.ru/article/7640

[15] памяти: http://www.braintools.ru/article/4140

[16] Источник: https://habr.com/ru/companies/fa/articles/945910/?utm_campaign=945910&utm_source=habrahabr&utm_medium=rss

www.BrainTools.ru

Rambler's Top100