Solidity — это компьютерный код (язык программирования), который запускает сеть Ethereum. Он позволяет создавать смарт контракты и Dapps на платформе, расширяя возможности сетей, таких как Биткоин.
В начале появился Биткоин. Это был быстрый и простой способ обмена денег децентрализованным способом — без участия банков. И люди могут доверять ему, потому что каждая транзакция записывается в публичную книгу, которую нельзя изменить.
Но Биткоин ограничен. Технология блокчейн могла сделать намного больше, чем обмены между равными валютами, если бы у нее был правильный код. В связи с этим, была создана сеть Ethereum, подкрепленная новым языком программирования под названием Solidity. В этой статье мы исследуем его происхождение и его цели.
ЧИТАЙТЕ
9 лучших платформ, которые поддерживают Solidity
Понимание основ смарт контрактов
В рамках разработки смарт контракт состоит из трех разделов: баланс, хранения и коды. Баланс представляет, сколько Ethereum имеет умный контракт. Хранилище содержит данные, такие как строки и массивы, которые являются специфическими для каждого приложения. Раздел кода содержит необработанный машинный код, который скомпилирован из того, что мы пишем в Solidity.
В отличие от учетных записей пользователей, учетные записи смарт-контракта не являются внешними по отношению к соответствующим сетям. Другими словами, вы можете использовать свой кошелек в различных сетях, таких как Kovan и Ropsten, но вы не можете сделать это с помощью умного контракта. Умные контракты являются внутренними.
Каждый смарт-контракт имеет источник, который хранится на устройстве автора и экземпляры, которые хранятся в блокчейне. Чтобы создать экземпляр (учетную запись) умного контракта, нам нужно развернуть его в сети. Он очень напоминает отношения между классами и экземплярами в традиционном объектно-ориентированном программировании (ООП) и представляющих его языках (JS, Ruby). Чтобы дать вам более наглядное представление, давайте создадим класс Bike и добавим его экземпляр.
Bike class & instance
Мы напишем определение контракта, которое затем будет запускаться через компилятор, который создаст два файла: байт-код и двоичный интерфейс приложения (ABI). Байт-код — это то, что будет фактически передано в EVM, а ABI — это слой между байт-кодом и обычным кодом JavaScript, который позволяет создавать пользовательский интерфейс (UI).
Как изучать
Официальных книг по Solidity пока не выходило, поэтому обучаться языку лучше всего по официальному релизу. Также есть платный образовательный 3-часовой курс на Udemy.
Для тех, кто пока не силен в английском, но очень хочет быть в теме, дорога одна — на Хабрахабр (, , ). Также есть подробный туториал на проекте Голос, правда, пока только один урок.
Кроме того, тема привлекает всё новых авторов, вебинары и статьи в личных блогах появляются достаточно часто. Периодически проверяйте популярные платформы и будете в курсе развития и применения языка.
Выбор IDE и версии Solidity
Прежде чем мы начнем, нам нужна надлежащая интегрированная среда разработки (IDE). Другими словами, нам нужен удобный терминал с необходимыми инструментами для написания нашего кода. Для целей данного руководства мы выберем Remix, IDE, созданную фондом Ethereum, которая позволяет писать, тестировать, отлаживать, запускать умные контракты и многое другое. Вы можете использовать его либо прямо в браузере, либо скачать его локально, если хотите.
После запуска Remix вы увидите редактор кода в центре, файловый менеджер слева и компилятор справа.
Initial Remix window
Будет какой-то заранее написанный код — он нам не понадобится. Чтобы создать первый уникальный умный контракт, давайте нажмем на значок плюса в верхнем левом углу терминала и дадим ему имя.
Creating a new project in Remix
Поскольку у нас есть пустой документ. sol, мы должны указать версию Solidity, которую будет запускать компилятор. На момент написания данного руководства последняя версия была 0.5.7. Если вы не уверены, какую версию использовать, вы можете указать диапазон версий.
2 типа указания версии Solidity
Давайте дадим нашему умному контракту имя, а затем круглые скобки.
Именование контракта
Синтаксис
В сравнении с JS есть пара существенных отличий:
- Solidity — статически типизированный язык, динамическими являются только возвращаемые типы.
- Полноценной версии языка не вышло (текущая — 0.4.16), поэтому многие функциональность пока в урезанном виде, а количество багов достаточно велико.
Однако с поставленными задачами Solidity справляется, а визуально это все тот же ECMAScript:
contract GavCoin { mapping(address=>uint) balances; uint constant totalCoins = 100000000000;
/// Endows creator of contract with 1m GAV. function GavCoin(){ balances[msg.sender] = totalCoins; }
/// Send $((valueInmGAV / 1000).fixed(0,3)) GAV from the account of $(message.caller.address()), to an account accessible only by $(to.address()). function send(address to, uint256 valueInmGAV) { if (balances[msg.sender] >= valueInmGAV) { balances[to] += valueInmGAV; balances[msg.sender] -= valueInmGAV; } }
/// getter function for the balance function balance(address who) constant returns (uint256 balanceInmGAV) { balanceInmGAV = balances[who]; } }
Написание вашего первого смарт контракта
Как только у нас будет готов холст, пришло время определить основные строительные блоки — переменные. Хотя у опытных разработчиков программного обеспечения не возникнет проблем с пониманием этой концепции, мы кратко представим ее новичкам. Переменные являются заполнителями для порций информации, на которые впоследствии ссылается программа, которая их запускает.
Давайте создадим пару переменных: строку (последовательность символов) и целое число (число). В случае Ethereum переменные хранятся в блокчейне вместе с остальными частями контрактов и следовательно могут быть доступны и обновлены из любого места. Другой ключевой характеристикой переменных Solidity является то, что вы можете сделать их приватными, написав «private» рядом с переменными. Наконец, для целых чисел Solidity имеет два типа: подписанный (может быть положительным и отрицательным) и беззнаковый (может быть только положительным). Чтобы указать неподписанную переменную, мы должны просто поставить ‘u’ перед ней.
A private string and an integer
Когда у нас есть переменная name, нам нужно написать методы установки и получения. Это похоже на функцию JS. Помните, что Solidity имеет статическую типизацию, поэтому мы должны определить типы переменных. Теперь любое значение, которое мы помещаем в ‘setName’, будет определять строку ‘name’. Для получения мы будем использовать getName и указать, какую переменную мы ожидаем увидеть. Теперь пришло время сделать то же самое для переменной age. Метод построен аналогично getName.
Name/age setters and getters
Давайте проверим наш маленький кусок кода. Перейдите на вкладку «Выполнить» (Run) компилятора и нажмите «Развернуть» (Deploy) под именем вашего контракта. В самом низу компилятора вы увидите раздел «Развернутые контракты» (Deployed Contracts), в котором доступны наши методы. Чтобы передать имя в значение «newName», нам нужно убедиться, что наша строка записана в JSON, в противном случае «getName» ничего не вернет. Для «setAge» просто укажите свой возраст без кавычек. Как вы видите, теперь мы можем устанавливать и получать переменные name и age через наш умный контракт.
Компилятор, с именем и возрастом
В прошлом генерация СЧ в блокчейне сулила катастрофу для безопасности
К счастью это изменилось. Если вы немного погружены в контекст, то я расскажу, как получилось решить эту проблему. Вместе разберемся, как сейчас можно сгенерировать подлинные случайные числа очень быстро.
А если вы чему-либо научитесь к концу этой статьи, то, умоляю, окажите мне и всему миру услугу: создайте толковый техасский холдем. Сам я не смог этого сделать пока что.
Зачем нам нужны СЧ?
Например, мы захотим провести лотерею, игру или же заполучить случайные данные для тестовых наборов данных. Раньше для этой задачи мы бы использовали актуальный хеш блока. Он бы стал основой для нашего генератора псевдослучайных чисел. Причем, это самый распространенный подход, который очень часто предлагают в туториалах.
Что такое Wei и Gas
Одна из самых замечательных особенностей смарт контрактов заключается в том, что для развертывания их в сети Ethereum вам потребуется инициировать транзакцию, которая стоит определенную сумму денег, которая выплачивается в Ether. Крайне важно понимать, как используются сборы в системе, так как они будут вычитаться при каждом взаимодействии с EVM.
Что такое Wei?
Давайте предположим, что читая наш урок, вы хотя бы раз использовали Биткоин. Вы, вероятно, сделали небольшую транзакцию, стоимость которой была меньше 1 BTC. В этом случае вы использовали сатоши, что-то вроде центов за доллар. Wei похож на сатоши — это самая маленькая часть 1 эфира. Если мы думаем об этом с точки зрения программирования, это самое низкое целое число без знака в сети. Взаимодействуя с сетью, вы чаще всего сталкиваетесь с Gwei, который относится к Gigawei и равен 1 млрд. Wei.
Что такое Gas?
Gas является неотъемлемой частью механизма умного исполнения контракта. Он имеет два значения для каждой транзакции: потребляемый газ и его цена. Стоит отметить, что пользователь инициирующий транзакцию, определяет сам эти значения. Однако, если установленного значения Gas недостаточно для выполнения определенной операции, то Gas будет потреблен, но транзакция не будет выполнена. Более того, если цена на газ будет установлена слишком низкой для сети в данный момент времени, транзакция не будет обрабатываться узлами, что в конечном итоге сделает ее неудачной. Существует несколько сервисов для проверки оптимальных значений для ваших транзакций, одна из которых — ethgasstation.info. Чтобы лучше понять Gas и то, почему он стоит каких-то денег, давайте начнем кодирование некоторых из них сами.
Вернитесь в окно Remix и создайте новый файл. В нашем примере мы назовем его «Gas» и создадим контракт с таким же именем. Имейте в виду, что чем больше данных нам потребуется хранить на блокчейне, тем больше газа нам понадобится. При этом для целей данного урока мы создадим дешевый контракт; Чем больше вы добавите к нему, тем выше будет плата.
Есть функция, которая возвращает целое число, которое является суммой двух входных данных. Чтобы сделать его как можно более легкой, мы укажем, что наш контракт ничего не будет хранить в блокчейне и для этого мы добавим «pure» рядом с функцией.
Дешевый контракт
Теперь вы можете развернуть его в компиляторе и ввести любые два числа, чтобы получить целое число «c». Чтобы проверить цену нашей транзакции, мы должны взглянуть на терминал, расположенный под разделом кода. Существует стоимость транзакции и стоимость исполнения. Первый относится к тому, сколько данных имеет транзакция. Второй относится к тому, сколько энергии EVM потребовалось для транзакции.
Дешевый контракт
Это очень простая транзакция, которая почти ничего не стоит для сети. При написании значимых умных контрактов вы добавите больше деталей, что увеличит их вес и следовательно комиссионные за транзакции.
Окончательный запрет устаревших конструкций
В Solidity накопилось довольно много устаревших конструкций, которые хотелось бы удалить из языка, но все никак не удается из-за обратной совместимости. В эту категорию изменений попадают:
- Отказ от throw в пользу revert()/assert(…)/require(…). Механизм откатывания транзакции отличается от механизма исключений, и хочется подчеркнуть эту разницу на уровне языка.
- Отказ от var, который с учетом правил вывода типов легко приводит к ошибкам, например: for(var i = 0; i < 100500; i++) { … // бесконечный цикл Дополнительно обсуждают , на что заменить конструкции вроде var (z, y, z) = foo(); и как поэлегантнее пропускать ненужные значения .
- Запретили использовать constant для функций — всюду должно быть view или pure.
- Встроенная функция gasleft() вместо msg.gas. Так понятнее, что это не какая-то константа, а оставшееся количество газа. Да, gasleft() можно переопределить.
- Перенесли block.blockhash в blockhash. Логично, ведь blockhash текущего блока недоступен (block.blockhash(block.number) == 0).
- Запретили смешивать шестнадцатеричные константы и множители времени/эфира. В самом деле, не совсем понятно, что такое 0xaf days.
- Отказались от suicide/sha3 в inline assembly. Не понятно, почему вне assembly эти конструкции по-прежнему доступны, хотя и deprecated.
- Отказ от унарного плюса, потому что он не имеет какой-то специальной роли и может участвовать в глупых ошибках (вроде x=+1; вместо x+=1;).
- Планируется отказ от множителя years, потому что сейчас он определен как 365 days, и это не слишком точно соотносится с привычным календарем. Мне это представляется спорным решением — казалось бы, пока можно ограничиться предупреждением.
Создание и развертывание собственного токена ERC20. Выпуск токенов и создание ICO.
Посмотрим правде в глаза, большинство разработчиков блокчейнов, которые только начинают стремятся играть по-крупному и создавать свои собственные блокчейны и токены. Хотя это чрезвычайно сложная тема, которая привлекла некоторых из лучших разработчиков программного обеспечения из других областей, создание базового токена ERC20 — не является сложной задачей.
Сначала нам нужно создать еще один файл в Remix и загрузить интерфейс ERC20, а именно:
Не про язык
Есть несколько очень важных изменений, которые не касаются непосредственно языка, но при этом сильно повлияют на код новых контрактов.
- Наконец-то появятся числа с дробной частью . Вроде все привыкли обходиться без них, а теперь будем учиться использовать их правильно .
- На уровне EVM добавится защита от short address attack. Казалось бы, мелочь, но приятно, что об этом не надо больше думать (и вообще знать об этой проблеме). Может быть, она будет даже более строгой , но там есть свои трудности.
Стандарт ERC20
Функция totalSupply позволяет нам увидеть, сколько у нас всего токенов. Функция balanceOf используется для получения количества токенов по определенным адресам. Функция transfer позволяет пользователям выполнять транзакции между собой. Функции «TransferFrom», «allowance» и «Approve» позволяют пользователям разрешать другим пользователям инициировать транзакции от их имени. События являются инструментами регистрации для главной книги.
В дополнение к самому интерфейсу нам понадобится отдельный файл. sol для нашего нового токена. Здесь мы импортируем интерфейс ERC20 и указываем символ, имя и десятичные числа нашего токена.
uToday token
Прежде чем мы скомпилируем его, нам нужно указать ограничения.
- Давайте начнем с общего предложения — это постоянная целочисленная переменная, которую мы сделаем приватной. Общий запас наших токенов составит 1 миллион, мы также напишем функцию для возврата этого значения.
- Во-вторых, нам нужно где-то хранить наш токен. Для этого нам нужно будет указать сопоставление, которое будет возвращать баланс для любого указанного адреса.
- В-третьих, должна быть функция для передачи токенов, которая по существу будет иметь адрес получателя и количество переданных токенов. Эта функция также должна быть в состоянии проверить, достаточно ли у отправителя количества токенов на его балансе, что можно реализовать с помощью простого оператора if / then. Кроме того, мы установим условия для ‘_value’ таким образом, чтобы пользователи не могли отправлять транзакции с «0» токенами, так как это приведет к засоренью сети разным мусором.
- В-четвертых, мы должны создать отображение для остальных функций, которое представляет собой отображение целого числа.
Затем мы укажем несколько проверок в функциях «утверждение» (approve) и «допуск» (allowance) и установим условия для ‘transferFrom’. Наконец, не все токены будут доступны на рынке. Некоторые из токенов обычно оставляются для команд, фондов, консультантов и других целей. Следовательно важно, чтобы мы ясно дали понять, сколько токенов будет циркулировать в системе. Когда мы создали токены, оборотное предложение равняется нашему балансу.
uToday token constraints
Код готов, поэтому давайте проверим его. Перейдите на вкладку «Выполнить» (Run) компилятора и разверните наш токен-контракт. Вы увидите, что у нас есть данные токена, а также общее предложение, остатки и надбавки. Поздравляю, вы создали свой первый токен. Чтобы наш токен действительно работал в сети, нам нужно развернуть умный контракт (обратите внимание, что это отличается от развертывания его для тестирования в Remix). Для этого урока мы будем использовать Remix и Metamask, но есть и другие способы сделать это. Metamask — это простая, но эффективная программа-кошелек Ethereum с приятным пользовательским интерфейсом, который интегрируется как расширение в некоторые из самых популярных браузеров. В нашем случае мы будем использовать Opera. Сначала перейдите на metamask.io и загрузите расширение. Как только это будет сделано, вы увидите значок лисы в правом верхнем углу вашего браузера.
Загрузка metamask и расположение иконки
Нажмите на значок и выполните предложенные инструкции для создания кошелька. Не забудьте сохранить секретную фразу! Когда у вас есть кошелек, нажмите на иконку Metamask и измените сеть на «Ropsten», потому что мы не хотим связываться с основной сетью Ethereum.
Изменение metamask на Ropsten
Последним шагом является создание некоторого Ether (к сожалению, вы не сможете использовать его для каких-либо реальных покупок, но они необходимы для тестирования). Зайдите на faucet.metamask.io и запросите 1 эфир. Теперь у вас все готово. Вернитесь в окно Remix и измените среду на «Injected Web3» в компиляторе. Взгляните также на вкладку учетной записи — ваш адрес должен совпадать с адресом, который вы создали в Metamask. Выберите смарт-контракт, который вы хотите развернуть, который является вашим токен-контрактом, но не интерфейсом ERC20 и нажмите соответствующую кнопку. Появится окно metamask с транзакцией, ее деталями и опциями для взаимодействия с ней. Отправьте транзакцию и наш токен оживет.
Metamask всплывающее окно
Теперь вы можете поиграть со всеми функциями, которые мы указали ранее. Давайте посмотрим на наш контракт с другой стороны, чтобы убедиться, что он работает правильно. Как и любой другой блокчейн, в Ethereum есть несколько block explorers, которые служат основной целью мониторинга того, что происходит в сети. В нашем случае мы будем использовать etherscan , хотя есть несколько других замечательных альтернатив. Обратите внимание, что если вы просто зайдете в etherscan, вы увидите Основную сеть. Поскольку нам нужно увидеть сеть Ropsten, вам нужно будет поставить «ropsten» перед адресом сайта. Найдите свой адрес и вы увидите две транзакции — одна для бесплатного эфира, который вы получили, а другая для развертывания контракта.
Адрес пользователя в Etherscan
Чтобы найти адрес вашего контракта, нажмите на TxHash и перейдите в поле «To». Здесь вы можете проверить транзакции, код и события вашего смарт-контракта. На данный момент нам нужно проверить и опубликовать наш контракт. Перейдите в раздел «Code» и нажмите ссылку «Проверить и опубликовать» ‘Verify and Publish’ . Здесь вам нужно будет снова указать имя вашего токена, версию компилятора (в нашем случае последняя использованная нами версия Solidity была 0.5.7, поэтому мы будем придерживаться соответствующей версии компилятора). Теперь вам нужно скопировать код смарт-контракта токена вместе с кодом интерфейса ERC20 из окна Remix в etherscan и нажать «Подтвердить и опубликовать» ‘Verify and Publish’ в нижней части экрана.
Проверка смарт контракта
Пришло время вернуться к адресу вашего контракта. Код на вкладке «Code» теперь будет подвержен проверке. Кроме того, теперь у вас появятся еще две вкладки: «Читать договор» и «Написать договор» ‘Read contract’ & ‘Write contract’. В разделе чтения мы можем проверить функциональность нашего токена. Введите свой адрес (не адрес контракта) в поле ‘balanceOf’, чтобы увидеть, сколько у вас токенов; должно показать 1 миллион, который мы жестко закодировали как общий запас и передали его в наш кошелек. Это означает, что наш токен теперь корректно работает в тестовой сети.
Получение баланса
Методы, возвращающие тип address
Ниже рассмотрим методы, возвращающие тип address.
msg.sender
msg.sender() возвращает address payable.
Как следует из названия, функция msg.sender возвращает адрес, который инициировал вызов этого смарт-контракта. Однако важно отметить следующее:
msg.sender возвращается в текущем вызове. Он не обязательно возвращает отправителя EOA, который отправил транзакцию.
- Если смарт-контракт A вызван непосредственно в транзакции отправленной с EOA, то в msg.sender будет адрес EOA.
- Если смарт-контракт A вызван другим смарт-контрактом B, где B был вызван транзакцией отправленной с EOA, то в msg.sender будет адрес смарт-контракта B.
tx.origin
Предупреждение: небезопасно!
tx.origin() возвращает address.
tx.origin возвращает EOA адрес отправителя изначальной транзакции. Таким образом, возвращается полная цепочка вызовов.
block.coinbase
block.coinbase() возвращает address payable.
Адрес добытчика текущего блока, т.е. адрес получателя платы за текущий блок и вознаграждения за блок.
ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)
ecrecover восстанавливает адрес, связанный с открытым ключом, из подписи эллиптической кривой или возвращает ноль при ошибке. Параметры функции соответствуют значениям ECDSA подписи:
- r = первые 32 байта подписи
- s = вторые 32 байта подписи
- v = последний 1 байт подписи
ecrecover возвращает address, а не address payable.
Послесловие
Если вы хотите начать карьеру в криптоиндустрии в кчестве разработчика, вы должны понимать что, несмотря на свою относительную простоту в основе, блокчейн обладает невероятной гибкостью и функциональностью. С 2022 года блокчейны значительно эволюционировали и их варианты использования выходили за рамки просто финансовых транзакций. С появлением Ethereum появился целый новый уровень сетей, на котором размещены различные приложения dApps и решения на основе блокчейна. Инструментом этой эволюции был смарт контракт и если вы хотите расширить свой опыт, сделать его более ценным и ориентированным на будущее, вы должны знать как он работает.
Если вы можете кодировать смарт контракты, используя другие языки, Solidity все таки лучше подходит для этих целей. Более того, если вы хотите стать разработчиком Ethereum или создавать токены ICO / ERC20 для своего проекта, это безусловно ваш выбор. Если у вас есть некоторый опыт работы с C ++ или JavaScript, кодирование на Solidity должно быть для вас относительно простым. Вам, однако, придется понимать некоторые различия между клиент-серверной и децентрализованной моделями запуска программного обеспечения. Благодаря Ethereum Foundation и некоторым сторонним организациям разработчикам предоставляется набор удобных инструментов, таких как Remix и Etherscan, для кодирования и развертывания интеллектуальных контрактов.
Мы надеемся, что наш учебник помог вам разобраться с большинством концепций Solidity, чтобы начать увлекательное путешествие по изучению технологии блокчейн. Помните, что вы всегда можете проверить с последней документацией Solidity
Улучшение «работы с памятью»
В кавычках, потому что речь в основном идет о «неожиданном» доступе к storage без использования assembly.
- Запрет непроинициализированных ссылок на storage, которые на деле указывали в начало storage, и поэтому пересекались с другими переменными состояния.
- Может быть запретят прямую работу с .length. Изменение длины массива вручную — достаточно низкоуровневая операция. Ее основной плюс — экономия газа при небольших изменениях длины массива. Но такой синтаксис позволяет случайно (или умышленно) создать массив неадекватного размера, что может привести к overlap attack. Для очистки массива давно есть delete, для уменьшения размера предлагается использовать pop(), помимо этого обсуждают операции вроде truncate() и extend(). Ну и по-прежнему есть assembly, если очень надо.
Применение нескольких модификаторов к функции.
К одной функции можно применить несколько модификаторов. Это можно сделать следующим образом:
contract OwnerContract { address public owner = msg.sender; uint256 public creationTime = now; modifier onlyBy(address _account) { require(msg.sender == _account, «Отправитель не авторизован.»); _; } modifier onlyAfter(uint256 _time) { require(now >= _time, «Функция вызвана слишком рано»); _; } function disown() public onlyBy(owner) onlyAfter(creationTime + 6 weeks) { delete owner; } }
Модификаторы будут выполняться в том порядке, в котором они определены, то есть слева направо. Так, в приведенном выше примере функция будет проверять следующие условия перед запуском:
- onlyBy(…) : является ли адрес, вызывающий контракт, владельцем?
- onlyAfter(…) : является ли вызывающий владельцем смарт-контракта более 6 недель?