Solidity. Смарт контракты и аудит – Telegram
Solidity. Смарт контракты и аудит
2.62K subscribers
246 photos
7 videos
18 files
547 links
Обучение Solidity. Уроки, аудит, разбор кода и популярных сервисов
Download Telegram
Тесты с урока про Honeypot

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

Однако напомню для повторения.

В начале он импортирует loadFixture, expect и ethers для проведения тестов, а также type для использования typechain.

import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { expect } from "chai";
import { ethers } from "hardhat";
import type { Bank, Attack, Logger, Honeypot } from "../typechain-types";

Затем в деплое, вместо beforeEach, пишет функцию dep(), которая возвращает объекты для тестирования ниже.

И в тестах начинает работы с получения этих объектов через await loadFixture(dep).

На данный момент это стандарт работы с тестами в hardhat и их нужно знать.

#honeypot #deploy
Solidity by Example

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

Я нашел один интересный сайт, где приводятся примеры кода Solidity, как шпаргалки.

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

Да, он на английском, но тем не менее очень понятен интуитивно.

Solidity by Example.

#links #hint
Статья с рекомендациями

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

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

#link #hint
Используйте assert() и require() правильно

Assert()
следует использовать только в тестах внутренних (internal) ошибок или для проверки инвариантов.

Require() используют для проверки условий.

#assert #require #hint
👍1
Используйте модификаторы правильно

Интересное замечание, которое я не встречал еще в практике.

Не рекомендуется использовать модификаторы, в которых изменяются переменные состояния или реализуются внешние вызовы, например при наследовании, так как сами модификаторы исполняются до кода в функции.

contract Registry {
    address owner;

    function isVoter(address _addr) external returns(bool) {
        // Code
    }
}

contract Election {
    Registry registry;

    modifier isEligible(address _addr) {
        require(registry.isVoter(_addr));
        _;
    }

    function vote() isEligible(msg.sender) public {
        // Code
    }
}

Например, выше вы можете видеть НЕ правильное использование модификатора, так как контракт Registry может делать reentrancy атаку в другом контракте, вызывая Election.vote() внутри isVoter().

Модификаторы чаще всего используются, чтобы заменить дублирующийся код в функциях, по примеру проверки владельца isOwner().

#modifier #hint
👍1
Аккуратнее с делением чисел

Solidity, на данный момент сентября 2022 года, не поддерживает числа с точкой, и при делении 5/2 будет показан результат "2". Т.е. вместе с откидыванием цифр после точки, он еще и округляет результат до меньшего числа.

Это действительно проблема для большинства разработчиков. И многие пытаются преодолеть ее через дополнительные библиотеки на openzeppelin или пишут свои "костыли".

В документации по Solidity пишут, что нужно использовать мультипликатор, как в примере:

uint multiplier = 10;
uint x = (5 * multiplier) / 2;

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

Некоторые предлагают использовать decimals эфира, но я не видел хороших примеров.

#division #integer #hint
👍1
Интерфейсы и абстрактные контракты

Интерфейсы и абстрактные контракты призваны помочь с написанием кода нашего контракта и облегчить его.

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

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

#abstract #interface #hint
👍1
Особенности fallback функций

Если fallback() указан как payable, то он может принимать Ether.

Особенность отправляющих Ether функций transfer и send в том, что у них есть ограничение - инициированная ими транзакция не должна расходовать больше, чем 2300 gas. Поэтому, если внутри fallback реализована какая-то сложная логика (вызвать еще какие-то функции, записать storage и т.д.) (при поступлении Ether с помощью transfer или send), то это будет стоить больше, чем 2300 и транзакция откатится.

Максимум на что хватит газа в такой ситуации внутри fallback - это на emit event.

При этом, если эфир отправляется с помощью call вызовов, то есть возможность повышения лимита газа, и тогда в fallback функция не откатит транзакцию.

Будьте аккуратны с этим, и всегда дополняйте fallback другими функциями, которые могут принимать деньги, например receive.

#hint #fallback #receive
Особенности модификатора payable

Если функция, не помеченная как payable, вызывается в другой функции помеченной как payable, то транзакция все равно сможет передать эфир, так как msg.value будет установлен.

#hint #payable
Версия pragma Solidity

В начале каждого файла нашего контракта мы указываем версию pragma. Другими словами, мы сообщаем версию языка Solidity, с которой работали.

Так вот, не уверен, насколько этот совет из статьи актуален сейчас, однако звучит достаточно здраво: фиксируйте версию pragma для своего контракта.

Если вы заметили, то мы обычно пишем так: "pragma solidity ^0.8.0;". И вот этот значок "^" указывает на то, что для контракта могут подходить версии 0.8.0 и выше.

И данная рекомендация, предлагает писать без этого знака, как бы говоря, что мы работаем только с этой версией Solidity.

Это может стать актуальным если, скажем, версия 0.9.0 введет изменения в языке и функциях, и тогда наши контракты будут выдавать ошибки, если указан "^".

Повторяю, не знаю, насколько это правильно в текущих реалиях, но доля логики здесь есть.

#hint #pragma
Используйте event для мониторинга

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

contract Charity {
    mapping(address => uint) balances;

    function donate() payable public {
        balances[msg.sender] += msg.value;
    }
}

contract Game {
    function buyCoins() payable public {
        // 5% goes to charity
        charity.donate.value(msg.value / 20)();
    }
}

Когда контракт Game сделает вызов функции Charity.donate(), эта транзакция не отобразится во внешних транзакциях Charity. Именно для таких целей лучше всего использовать event, которые будут порождать события при совершении переводов.

#hint #event
👍1
Аккуратнее со встроенными функциями

В Solidity существуют встроенные функции, которые доступны в написании контракта по умолчанию, как например revert() или selfdestruct().

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

contract PretendingToRevert {
    function revert() internal {}
}

contract ExampleContract is PretendingToRevert {
    function somethingBad() public {
        revert();
    }
}

В примере, вызов функции revert() выполнит не откат транзакции, как это должно быть, а условие из PretendingToRevert.

Будьте внимательны и всегда проверяйте исходный код наследуемых контрактов.

#hint #build-in
Избегайте tx.origin

Никогда не используйте tx.origin для проверок или авторизации, например так "require(tx.origin == owner)"
. В этом случае другой контракт (хакер) может получить доступ к вашему контракту и вывести все деньги.

Вместо этого используйте msg.sender.

Более того, tx.origin может быть выведен из языка в последующих обновлениях Solidity.

#hint #tx #tx.origin
Используйте адрес типа интерфейса вместо простого типа адреса

Для большей безопасности в контракте следует использовать interface type вместе обычного типа address в аргументах функции.

contract Validator {
    function validate(uint) external returns(bool);
}

contract TypeSafeAuction {
 
    // good
    function validateBet(Validator _validator, uint _value) internal returns(bool) {
        bool valid = _validator.validate(_value);
        return valid;
    }
}

contract TypeUnsafeAuction {
 
    // bad
    function validateBet(address _addr, uint _value) internal returns(bool) {
        Validator validator = Validator(_addr);
        bool valid = validator.validate(_value);
        return valid;
    }
}

#hint #tx #tx.origin
Урок 27 - ERC1155: NFT и взаимозаменяемые токены

Ну, вы, конечно, монстры! Я думал, что после вчерашнего залпа постов в течение дня, с канала отпишутся несколько человек. Но все на месте! Это очень круто!

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

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

Новое видео про ERC1155.

Ну, что же, приятного просмотра и легкого обучения!

#урок #erc1155 #nft
👍1
Кратко о различиях ERC721 и ERC1155

Вчера я немного опоздал на стрим и пропустил момент, когда лектор рассказывал о различиях этих двух стандартов. И до сегодняшнего дня меня не покидало ощущение, что я что-то где-то "недогнал".

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

Итак, по сути, ERC1155 - это просто улучшенная версия ERC721. Чтобы создать свой NFT в ERC721 нужно было каждый раз создавать новый контракт, при этом каждый токен мог быть исключительно в одном экземпляре.

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

Сейчас попробую описать это на примере.

Возьмем для примера игру Counter Strike. Это такая стрелялка, где команда спецназа борется с командой террористов, и там существует несколько видов огнестрельного оружия.

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

Разработчики игры могут вводить эти скины в игру за дополнительную оплату. При этом, они могут выпустить всего 10 скинов - лимитировать спрос. И только 10 человек от всех игроков будут иметь их в своем арсенале. А могут выпустить и 1 уникальный легендарный скин, который может выкупить только один игрок, и потом хвастаться всем остальным, какой он уникальный.

Другими словами, в рамках ERC1155 мы можем выпускать как уникальные NFT в единственном экземпляре, так и NFT в 2, 5, 20 экземплярах и более. И все они будут управляться одним нашим контрактом!

Именно за свою гибкость и новый подход, ERC1155 уже пришел на смену ERC721.

Поэтому, если вы захотите создать свой NFT, то лучше писать его сразу на ERC1155.

#erc1155 #nft
👍1
Разбор контракта ERC1155. Часть 1

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

Для начала здесь подключаются три интерфейса:

import "./IERC1155.sol";
import "./IERC1155MetadataURI.sol";
import "./IERC1155Receiver.sol";

Первый описывает функции нашего контракта, второй дополняет его с реализацией  function uri(), которая возвращает ссылку на токен по его id, а третий - знакомый нам по ERC721, который помогает делать проверку другого контракта на возможность приема токенов.

Особенность данного стандарта в том, что тут есть функции, как для передачи одного токена, так и их группы (Batch).

Далее вводятся два mapping: одни - на проверку "какое количество данного токена есть на конкретном адресе", и второй - "который говорит, что тот или иной адрес (оператор) имеет право распоряжаться токенами на другом конкректом адресе".

И добавляем переменную string, которая будет хранить ссылку на все наши токены. Например, там будет храниться ссылка "https://alltokens.eth/myToken/...", и на месте троеточия будет подставляться id конкретного токена после.

Далее в конструкторе принимает строку ссылку и передает ее в функцию setURI(), которая устанавливает новою ссылку от разработчика.

Также есть простая функция uri(), которая просто возвращает саму ссылку.

balanceOf(), уже знакомая нам, показывает, сколько токенов на определенном адресе.

setApprovalForAll() - выдает разрешение на управление нашими токенами оператору через служебную функцию _setApprovalForAll() с дополнительными проверками на владельца, и isApprovedForAll(), соответственно, проверяет это разрешение.

Интерес тут представляет balanceOfBatch(), которая в качестве аргументов принимает массивы адресов и id токенов и возвращает массив с их количеством на адресе. Для этого делается проверка, что длина массива с аккаунтами равна длине с id токенов, так как, если, например, количество адресов будет больше, то где-то совершена ошибка.

Там же создается новый массив с фиксированной длинной. Мы же помним, что в memory можно создавать только такие массивы?

И через цикл мы прогоняем адреса, вызывая функцию balanceOf() для каждого адреса и id токена, записывая все в новый массив, который и возвращаем.

Еще нужно отметить, что в ERC1155 решили отказаться от обычной функции transferFrom(), и заменить ее safeTransferFrom(), которая выполняет дополнительные проверки перед пересылкой токенов.

#erc1155 #nft
Разбор контракта ERC1155. Часть 2

Теперь самое интересное!

Есть две функции пересылки токенов _safeTransferFrom() и _safeBatchTransferFrom(), которые с небольшой разницей делают одно и тоже: порождают события о транзакции, вызывают дополнительные служебные функции, которые нам известны с прошлых стандартов, _beforeTokenTransfer() и _afterTokenTransfer(), а также вызывают новую функцию с длинным названием _doSafeTransferAcceptanceCheck().

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

Для этого используется служебная функция _asSingletonArray(). Простая, но очень полезная.

Она принимает число, как аргумент, и возвращает массив. Там просто создается новый массив через new result = uint[](1), так как нужна фиксированная длина, и записывается аргумент - result[0] = el.

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

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

В прошлых описаниях стандартов мы использовали assembly, чтобы получать ответ с ошибкой в try-catch. Но сейчас Solidity позволяет делать это своими средствами.

try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns(bytes4 resp) {
    if(resp != IERC1155Receiver.onERC1155Received.selector) {
     revert("Rejected tokens!");
    }
} catch Error(string memory reason) {
    revert(reason);
} catch {
     revert("Non-ERC1155 receiver!");
}

Если в ответе мы получаем селектор функции, которая равен тому, что мы отправили, то транзакция проходит без проблем. В остальных случаях мы ловим ошибку.

В рамках исполнения функции _doSafeTransferAcceptanceCheck() и _doSafeBatchTransferAcceptanceCheck() абсолютно одинаковые.

Вот и все! Теперь мы знаем еще и реализацию стандарта ERC1155.

#erc1155 #nft
Урок 28 - Вопросы к собеседованию. Часть 2

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

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

Видео с вопросами для собеседования.

Завтра я соберу все пройденные уроки этой части обучения в один пост для более удобной навигации.

Конечно же, канал не закрывается и не прекращает обучение.

Думаю, на следующей неделе мы возьмем небольшую пазу от видео уроков. Я буду постить материалы и статьи, которые раскроют другие темы в обучении, типа uniswap, ipfs, solmate, openzeppelin и другие. Плюс в течение этой недели я постараюсь подготовить список уроков и направление для дальнейшего изучения языка.

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

Я даже в начале не подозревал то, чтобы учить Solidity нужно уже иметь хорошие навыки работы с js, node, npm, а еще лучше с react.

Это очень круто, что вы взялись за это!

#урок #собеседование
1🔥1
Продвинутое обучение Solidity

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

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

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

Урок 17 - Голландский аукцион

Урок 18 - ERC20

Урок 19 - MultiSig и Timelock

Урок 20 - Паттерн commit/reveal

Урок 21 - DAO и Governance

Урок 22 - Паттерн Proxy/Upgradeable: Transparent, UUPS

Урок 23 - Typechain и hardhat toolbox

Урок 24 - ERC721 и NFT

Урок 25 - Rinkeby, Etherscan, Alchemy

Урок 26 - Honeypot

Урок 27 - ERC1155: NFT и взаимозаменяемые токены

Урок 28 - Вопросы к собеседованию. Часть 2

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

Более того, напоминаю, что вы всегда можете задавать вопросы в чатах:

Чат от канала - https://news.1rj.ru/str/+CqKZNgqZ640wNDdi
Чат от лектора - https://news.1rj.ru/str/joinchat/MxYT6-01eeA1NTYy

Далее мы будем рассматривать темы с других видео на YouTube, а уроки Ильи мы будем проходить по мере их выпуска на его канале.
👍1
Solidity. Смарт контракты и аудит pinned «Продвинутое обучение Solidity После изучение основ языка, необходимо знать и понимать, как он функционирует, какие паттерны существуют, и на что обращать внимание в коде, чтобы повысить его безопасность. В этих уроках рассматривается огромный пласт Solidity…»