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

Вообще, как я понял из уроков, есть два типа деплоя контрактов:

1. Деплой в hardhat для проведения тестов с функциями контрактов;
2. Общий деплой в блокчейн;

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

Для деплоя контракта используется уже знакомый нам код:

async function main() {
  const [signer] = await hre.ethers.getSigners();
  const Lock = await hre.ethers.getContractFactory("Lock", signer);
  const lock = await Lock.deploy();

  await lock.deployed();

}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Нужно сказать, что чаще всего код для деплоя в сеть пишут в файле с таким же названием - deploy.js.

После этого, вам нужно скомпилировать ваш контракт с помощью команды npx hardhat compile, и далее развернуть его в сеть с помощью:

npx harhat run noscripts\deploy.js --network localhost

Все! Теперь можно создать другой файл, подключить в него ethers.js и проводить какие-либо работы с развернутым контрактом уже в сети hardhat.

#deploy #hardhat
Немного о сетях и сервисах

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

Ранее существовали популярные тестовые сети:

1. Rinkeby
2. Ropsten
3. Kiln
4. Kovan

Так вот, они будут удалены  из-за слияния сетей Эфира в середине сентября 2022 года. Другими словами, их не нужно использовать вообще!

На их смену придут две новые сети:

1. Gorly
2. Sepolia

Они, вероятнее всего, станут основными тестовыми сетями для Эфира и всех токенов на его основе.

Поэтому в видео уроке необходимо взять общий принцип разворачивания СК, а не пример с Rinkeby.

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

1. Alchemy
2. Infura

Оба бесплатные, но с ограничениями. Для обучения подходят оба. Да и в вакансиях чаще всего встречаются только эти два.

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

Далее перейдем к деплою контракта и env.

#infura #rinkeby #ropsten #alchemy #gorly #sepolia
Настройка и использование .env

ENV (environment variables)
- это особые переменные рабочей среды. Другими словами, это некие глобальные значения, расположенные на уровне операционной системы, доступные программам, например настройки системы.

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

Устанавливается он при помощи команды в консоли:

npm install dotenv --save

Или как было показано в видео: через файл hardhat.config.json и команды в консоли после - npm install.

Затем в тот же файл hardhat.config добавляете строку подключения:

require('dotenv').config()

Далее вы заходите или создаете папку .env в корневом каталоге проекта и записываете переменные (в нашем случае ключи и доступы), например так:

S3_BUCKET="YOURS3BUCKET"
SECRET_KEY="YOURSECRETKEYGOESHERE"

Также проверьте, что в файле gitingnore есть запись ".env", чтобы случайно ваши пароли и ключи не попали в открытый доступ.

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

{process.env.API_KEY} - где API_KEY - имя вашей переменной.

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

#env
Пример деплоя контракта в сеть Goerli

И наконец, давайте попробуем загрузить наш контракт в тестовую сеть goerli.

Хочу уточнить, что после тестов мне больше понравился Alchemy, поэтому я буду использовать в примере его сервис.

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

Заходим в Alchemy и кликаем на кнопку Create App. Появляется окошко с полями. Там мы вводим название проекта, его описание, а также в поле Chain должен значиться Ethereum и в Network - Goerly.

С помощью Alchemy можно загружать контракты и в другие блокчейн сети, как Solana или Polygon, но мы работаем именно с Эфиром.

После этого создастся ваш проект. В рамках поля проекта ищите кнопку View key и нажимаете на нее. Копируете API key в свой env файл в новую переменную, например API_KEY.

Теперь заходим на сайт Метамаск и скачиваем приложение для Chrome.

Запишите, сохраните, сфотографируйте секретную фразу восстановления МетаМаск!!! Это очень важно! Если вдруг вы забудете пароль, это будет единственный способ восстановить доступ!

После установки и регистрации, открываете приложение и в самом верху выбираете "Тестовая сеть Goerly".

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

Теперь идем в папку нашего проекта и открываем файл hardhat.config.json.

В раздел module.exports добавляем код networks, как в примере лектора в уроке или как представлено ниже, чтобы в итоге он выглядел так:

module.exports = {
  solidity: "0.8.9",
  networks: {
    goerli: {
      url:
https://eth-goerli.alchemyapi.io/v2/${process.env.API_KEY},
      accounts: [process.env.META_KEY]
    }
  }
};

И вот после всех этих действий, мы можем загрузить свой проект в goerly, выполнив команду в консоли:

npx harhat run noscripts\deploy.js --network goerly

В вашем проекте на Alchemy должен появиться новый контракт, если все прошло как надо.

#goerly #metamask #alchemy
Как получить эфир для сети Goerli

Кстати, для все операций теперь в тестовой сети вам может потребоваться Эфир. Для этого в google вводите в строку поиска goerli faucet.

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

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

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

Так можно делать на других сайтах и собрать чуть больше эфира на кошельке для тестов с контрактом.

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

#goerly #metamask #faucet
Кратко о работе с Etherscan

Etherscan - это самый популярный сайт, где можно просматривать контракты, их код, abi и транзакции по ним.

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

Для этого после регистрации на Etherscan в правом верхнем углу кликаем на аватар своего профиля и выбираем Api keys. Создаем свой ключ и копируем его в новую переменную в файле env, например ETH_KEY.

Затем снова открываем файл hardhat.config.json и в раздел module.exports добавляем:

etherscan: {
  apiKey:
${process.env.ETH_KEY}
}

К слову сказать, у вас уже должен быть подключен новый пакет hardhat-etherscan (в toolbox он уже есть). Если нет, то сделать это можно командой в консоли:

npm install --save-dev @nomiclabs/hardhat-etherscan

и добавлением записи в hardhat.config.json:

require("@nomiclabs/hardhat-etherscan");

После этого в консоли вам станет доступна команда:

npx hardhat verify --network netName contractAddress

где netName - имя тестовой сети, куда вы загрузили свой контракт, а contractAddress - адрес вашего контракта.

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

#etherscan
Урок 26 - Honeypot

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

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

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

Новый видео урок.

Также напоминаю, что завтра у нас будет стрим от Ильи в 19:00 по московскому времени на его канале. Можно будет узнать новую тему, которую мы будем разбирать в пятницу, и задать вопросы по Solidity.

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

На этой неделе мы закончим продвинутое обучение и со следующей я расскажу вам о новых планах.

Всем легкого дня и быстрого обучения!

#урок #honeypot
Что такое Honeypot

И по традиции, давайте разберемся, что же такое этот Honeypot.

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

Допустим, по всем каналам в Телеграм и на популярных ресурсах кто-то делает посты о том, что он запускает крутые токены в оборот, что у него большие планы, и уже несколько крупных компаний поддерживают его инициативу. Конечно же, он предлагает всем пользователям купить его токены по бросовой цене в 0.1 $ и обещает, что через полгода их стоимость будет около 1 $ или даже 5 $. Доверчивые люди бросаются покупать токены в надежде заработать. Но через указанный промежуток времени, никакого роста токена нет, и пользователи решают продать его.

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

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

#honeypot
👍1
Разбор Honeypot

Для того, чтобы вы понимали лучше разбор данного урока, я скидываю ссылку на комит урока от лектора. Лучше всего будет, если вы прочитаете пост, а затем еще раз сами просмотрите код контрактов.

Как я понял из урока, honeypot основан на подмене контракта для логирования событий в контракте банка. Вроде как, всем пользователям показывается первый обычный контракт Logger, который просто порождает событие для log, а на самом деле, наследование идет от другого контракта, который был как бы спрятан от глаз пользователей.

И если не разобраться, от кого идет наследование в ILogger public logger в контракте банка, то можно попасть на деньги.

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

Хакер думает, что раз "balances[_initiator] = 0" вызывается после отправки средств на счет пользователя, то можно создать новый контракт для атаки, который будет вызывать функцию _withdraw каждый раз по новому пока на счету банка не останется средств.

И вот для того, чтобы обмануть хакера, который хочет обмануть нас, в контракте банка мы создаем новую переменную "bool resuming", чтобы запутать хакера, и в функции _withdraw принимаем дополнительный аргумент "uint _statusCode".

После этого, уже как бы мы сами создаем контракт honeypot и обновляем withdraw с новой bool переменной.

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

Уже в нашем honeypot мы используем "_actionCode", чтобы определить, кто пытается вызвать withdraw.

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

Если же мошенник пытается использовать уязвимость reentrancy и вызывать повторно withdraw, то через нее мы вызываем служебную _widthdraw с новым передаваемым аргументом "_statusCode", который на втором вызове меняет значение на "2". Теперь сам мошенник попадает в нашу ловушку honeypot, транзакция откачивается и он не может получить даже свои деньги.

Вот как-то так.

В 4 контрактах и 1 интерфейсе из урока легко запутаться, полагаю как и в данном посте при первом прочтении.

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

#honeypot
Тесты с урока про 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