Я работал со смарт-контрактами на протяжении 4-х лет, в основном на блокчейне Биткойна. Вот некоторые проекты, где мне довелось участвовать:Proof of Existence, bitcore, Streamium. В последние месяцы я работал с платформой Эфириум и изучал её. Я решил написать короткое руководство для будущих программистов, оно поможет вам легко начать писать свои первые контракты для Эфириума. Я разделил статью на 2 части: с чего начать построение контрактов Эфириума, и короткие заметки по поводу безопасности контрактов. Первые шаги в создании Смарт-Контрактов на базе Эфириума 0. Базовые принципы Данное руководство подразумевает, что у вас имеется базовый уровень знаний о технических аспектах работы криптовалют и в частности блокчейнов. Если у вас их нет, я рекомендую прочесть книгу «Mastering Bitcoin» Андреаса Антонопоулоса, работу Consensys «Достаточно Биткойна. Время Эфириума» («Just Enough Bitcoin for Ethereum»), или, хотя бы, посмотрите это короткое видео со Скоттом Дрисколлом. Чтобы продолжать, вам нужно знать, что такое секретный и публичный ключ, почему блокчейну нужны майнеры, как достигается децентрализованный консенсус, что такое транзакция, и какую роль играют скрипты в транзакциях, и конечно же разобраться с концепцией смарт-контрактов. Две другие важные и относящиеся к теме идеи, которые вам нужно понимать перед началом работы с Эфириумом, это Ethereum Virtual Machine (виртуальная машина Эфириума) и топливо. Эфириум был разработан как платформа смарт-контрактов. Его корни тесно вплетены в критику, Виталика Бутерина в сторону Биткойна, согласно которой возможности этой платформы для смарт-контрактов весьма ограничены. Виртуальная Машина Эфириума (EVM) — среда, в которой работают смарт-контракты на базе Эфириума. Для написания скриптов, он предоставляет более выразительный и полный язык, в отличии от Биткойна. По-факту, это Тьюринг-полный программный язык. Хорошая метафора – EVM это распределенный глобальный компьютер, в котором выполняются все смарт-контракты. Учитывая, что смарт-контракты работают в EVM, должен быть механизм ограничения ресурсов для каждого контракта. Каждая отдельная операция, выполняемая внутри EVM, на самом деле выполняется одновременно каждым узлом в сети. Вот почему существует топливо. Код контракта транзакции Эфириума может провоцировать запись и чтение данных, делать дорогие вычисления вроде использования принципов криптографии, делать звонки (отправлять сообщения) другим контрактам, и так далее. Каждая из этих операций имеет стоимость, измеряемую топливом, и каждая потребленная транзакцией единица топлива должна быть оплачена Эфиром, согласно цены на топливо/Эфир, которая изменяется динамически. Эта стоимость отнимается из аккаунта Эфириума, который выполняет транзакцию. Также, транзакции имеют лимит по топливу, этот параметр является верхней планкой того, сколько топлива может израсходовать транзакция, и используется в качестве предохранителя против программных ошибок, которые могут опустошить кошелек пользователя. Вы можете почитать подробнее про топливо здесь. 1. Настройка окружения Так, вы знакомы с основами, теперь давайте перейдем к коду! Чтобы начать создавать Аpps для Эфириума (а точнее — Dapps, т. е. Децентрализованные приложения, как многие любят их называть), вам будет нужен клиент для соединения с сетью. Он будет вашим окном в децентрализованную сеть, и покажет содержимое блокчейна, где отображается полностью состояние EVM. Есть несколько совместимых клиентов для протокола, самый популярный — Geth, приложение, написанное на Go. Так или иначе, он не самый дружественный к разработчику. Самая лучшая опция, найденная мной, это testrpc узел (да, название отстой). Поверьте, он сэкономит вам много времени. Установите его и запустите: Code: $ sudo npm install -g ethereumjs-testrpc $ testrpc Вам нужно будет запустить ‘testrpc’ в новом терминале и оставлять его работающим каждый раз, как вы занимаетесь кодингом. Каждый раз, как вы запускаете программу, она генерирует 10 новых адресов с ‘демо’ деньгами на них. Эти не настоящие деньги и предназначены для ваших тестов, вы можете пробовать что угодно и не бояться потери средств. Самый популярный язык программирования смарт-контрактов Эфириума — Solidity, мы будем использовать его. Также мы используем среду разработки Truffle, которая помогает в создании смарт-контрактов, компиляции, запуске и тестировании. Давайте начнем: Code: # Для начала, запустим Truffle $ sudo npm install -g truffle # настроим наш проект $ mkdir solidity-experiments $ cd solidity-experiments/ $ truffle init Truffle создаст все файлы, необходимые для экспериментального проекта, включая контракты для MetaCoin — примера токеновых контрактов. Вы должны суметь скомпилировать тестовый контракт командой ‘truffle compile’. Затем, чтобы запустить контракты в симуляционную сеть при помощи testrpc узла, который мы запустили, вам нужно запустить ‘truffle migrate’. Code: $ truffle compile Compiling ConvertLib.sol... Compiling MetaCoin.sol... Compiling Migrations.sol... Writing artifacts to ./build/contracts $ truffle migrate Running migration: 1_initial_migration.js Deploying Migrations... Migrations: 0x78102b69114dbb846200a6a55c2fce8b16f61a5d Saving successful migration to network... Saving artifacts... Running migration: 2_deploy_contracts.js Deploying ConvertLib... ConvertLib: 0xaa708272521f972b9ceced7e4b0dae92c77a49ad Linking ConvertLib to MetaCoin Deploying MetaCoin... MetaCoin: 0xdd14d0691ca607d9a38f303501c5b0cf6c843fa1 Saving successful migration to network... Saving artifacts... //Заметка пользователям MAC: //иногда Truffle выдает ошибку из-за файлов .DS_Store. Если у вас будет ошибка с упоминанием этих файлов, просто удалите их. Мы только что запустили контракты-примеры в нашу тестовую сеть. Уиихааа! Это было просто, не так ли? Настало время создать наш собственный контракт! 2. Создаем ваш первый контракт в Эфириуме В этом руководстве мы будем писать простой смарт-контракт Proof of Existence. Идея состоит в создании цифрового нотариуса, который сохраняет хэши документов как доказательство того, что они существовали на определенный момент времени. Используйте ‘truffle create:contract’ чтобы начать: Code: $ truffle create:contract ProofOfExistence1 Теперь, откройте contracts/ProofOfExistence1.sol в своем любимом текстовом редакторе (Я использую vim с подсвечиванием синтаксиса Solidity), и вставить всю нижеследующую версию кода: Code: // Proof of Existence contract, version 1 contract ProofOfExistence1 { // состояние bytes32 public proof; // вычислить и сохранить доказательство для документа // *транзакционная функция* function notarize(string document) { proof = calculateProof(document); } // функция-помощник для извлечения sha256 хэша документа // *read-only функция* function calculateProof(string document) constant returns (bytes32) { return sha256(document); } } Ну, именно с этого частично неверного и простого кода мы начнем создание, попутно найдя лучшее решение. Это — контракт на Solidity, напоминает класс в других языках программирования. Контракты имеют состояние и функции. Можно различать два типа функций, которые могут появиться в контракте: Только для чтения (постоянные функции): данный тип функций никогда не меняет состояние. Они только читают его, производят вычисления, и возвращают значения. Так как эти функции могут быть решены локально каждым узлом, они не стоят ни капли топлива. Обозначены ключевым словом ‘constant’. Транзакционные функции: функции, которые привносят изменения в состояние контрактов или отправляют средства. Так как эти изменения должны быть отражены в блокчейне, выполнение транзакционной функции подразумевает отправку транзакции в сеть и трату топлива. Наш контракт выше имеет по одной функции каждого вида, помеченные в документации. Посмотрим, как данная функция изменяет способ нашего взаимодействия с контрактом в следующей секции. Эта простая версия хранит только одно доказательство за раз, используя тип данных bytes32, или 32 bytes, что является размером хэша SHA256. Транзакционная функция ‘notarize’ позволяет человеку сохранить хэш заверяемого документа в переменной, записанной в состоянии контракта. Эта переменная — ‘proof’. Итак, эта переменная — публичная, и является единственным способом для пользователя данного контракта нотариально заверить документ. Мы скоро сами начнем это делать, но сначала… Давайте отправим в сеть ProofOfExistence1! На этот раз, вам придется редактировать файл миграции (migrations/2_deploy_contracts.js) чтобы заставить Truffle отправить наш новый контракт. Замените содержимое файла следующим кодом: Code: /* * migrations/2_deploy_contracts.js: */ module.exports = function(deployer) { deployer.deploy(ConvertLib); deployer.autolink(); deployer.deploy(MetaCoin); // добавить эту линию deployer.deploy(ProofOfExistence1); }; Вы также опционально можете удалить строки о ConvertLib и MetaCoin, которые мы более не будем использовать. Чтобы снова запустить эту миграцию, вам понадобится использовать флаг сброса, чтобы убедиться, что он снова запустится. Code: truffle migrate --reset Больше насчет того, как работает Truffle, можно почитать здесь. 3. Взаимодействие с вашим смарт-контрактом Теперь, когда наш контракт отправлен, давайте с ним поиграем! Мы можем посылать в него сообщения, используя функциональные звонки, а также читать его публичное состояние. Для этой цели, мы будем использовать консоль Truffle: Code: $ truffle console // получить выложенную версию нашего контракта truffle(default)> var poe = ProofOfExistence1.deployed() // и вывести ее адрес truffle(default)> console.log(poe.address) 0x3d3bce79cccc331e9e095e8985def13651a86004 // давайте зарегистрируем наш первый "документ" truffle(default)> poe.notarize('An amazing idea') Promise { <pending> } // теперь давайте получим доказательство для этого документа truffle(default)> poe.calculateProof('An amazing idea').then(console.log) Promise { <pending> } 0xa3287ff8d1abde95498962c4e1dd2f50a9f75bd8810bd591a64a387b93580ee7 // Чтобы проверить, было ли корректно изменено состояние контракта: truffle(default)> poe.proof().then(console.log) 0xa3287ff8d1abde95498962c4e1dd2f50a9f75bd8810bd591a64a387b93580ee7 // Хэш совпадает с тем, что мы вычислили ранее Заметьте, что каждый функциональный вызов возвращает Обещание Promise), и мы используем ‘ .then(console.log’ чтобы вывести результат, как только Обещание выполнено, если хотим его проверить. Первое, что мы делаем, получаем представление про наш отправленный в сеть контракт и сохраняем его в переменной под названием ‘poe‘. Затем, мы вызываем транзакционную функцию ‘notarize’, которая включает изменение в состоянии контракта. Когда мы вызываем транзакционную функцию, мы получаем Обещание, привязанное к ID транзакции, а не к тому, что возвращает сама функция. Помните, что для изменения состояния EVM нам нужно тратить топливо и отправлять транзакцию в сеть. Вот почему мы получаем ID транзакции в качестве результата Обещания; о транзакции, которая и привела к этому изменению состояния. В нашем случае, мы не заинтересованы в ID транзакции, так что мы просто удаляем Обещание. В случае написания реального контракта, мы бы захотели сохранить это, дабы проверить итоговую транзакцию и выловить ошибки. Далее, мы вызываем read-only (constant) функцию, ‘calculateProof’. Не забывайте отмечать ваши read-only функции ключевым словом ‘constant’, иначе Truffle попробует создать транзакцию, чтобы выполнить функцию. Здесь заключен способ сообщить Truffle, что мы не взаимодействуем с блокчейном, а просто читаем из него. Используя эту read-only функцию, мы заполучим SHA256 хэшсумму вашего «Офигенная идея» документа. Теперь нужно сравнить это с состоянием смарт-контракта Эфириума. Чтобы проверить, что состояние изменилось правильно, нам необходимо прочитать ‘proof’ переменную публичного состояния. Чтобы получить значение переменной публичного состояния, мы можем вызвать функцию с таким же именем, которая возвращает Обещание. В нашем случае, выводимый хэш — точно такой же, так что не стоит о чем-то волноваться. За подробной информацией о том, как взаимодействовать со смарт контрактами, обратитесь к этой секции документации Truffle. Как вы можете видеть из нескольких абзацев выше, наше первое видение Proof of Existenсe смарт-контракта действительно работает! Отличная работа! Однако, это удобно только для регистрации одного файла за раз. Давайте создадим улучшенную версию. 4. Повторение кода контракта Давайте так изменим код контракта, чтобы он стал поддерживать запись нескольких файлов одновременно. Скопируйте оригинальны файл с именем contracts/ProofOfExistence2.sol и примените данные изменения. Главные изменения состоят вот в чем: мы изменяем переменную proof в диапазон bytes32, называя ееproofs, мы делаем ее приватной, и мы добавляем функцию проверки, был ли документ уже заверен путем итерации в этом диапазоне. Code: // Proof of Existence contract, version 2 contract ProofOfExistence2 { // состояние bytes32[] private proofs; // сохраним доказательство существования в состоянии контракта // *транзакционная функция* function storeProof(bytes32 proof) { proofs.push(proof); } // вычислим и сохраним доказательство для документа // *транзакционная функция* function notarize(string document) { var proof = calculateProof(document); storeProof(proof); } // вспомогательная функция для получения sha256 хэш суммы документа // *read-only функция* function calculateProof(string document) constant returns (bytes32) { return sha256(document); } // проверить, был ли документ нотариально заверен // *read-only функция* function checkDocument(string document) constant returns (bool) { var proof = calculateProof(document); return hasProof(proof); } // возвращает "верно"(true) если доказательство сохранено // *read-only функция* function hasProof(bytes32 proof) constant returns (bool) { for (var i = 0; i < proofs.length; i++) { if (proofs[i] == proof) { return true; } } return false; } } Давайте взаимодействовать с новыми функциями: (не забудьте обновить migrations/2_deploy_contracts.js, чтобы включить новый контракт и запустить ‘truffle migrate —reset’) Code: // выложить контракты truffle(default)> migrate --reset // Получить новую версию контракта truffle(default)> var poe = ProofOfExistence2.deployed() // Давайте посмотрим, есть ли новый документ, а его быть не должно truffle(default)> poe.checkDocument('hello').then(console.log) Promise { <pending> } false // Теперь давайте добавим тот документ в базу доказательств truffle(default)> poe.notarize('hello') Promise { <pending> } // Теперь давайте снова проверим, был ли заверен документ? truffle(default)> poe.checkDocument('hello').then(console.log) Promise { <pending> } true // Успех! // мы также можем хранить другие документы, они также записаны truffle(default)> poe.notarize('some other document'); truffle(default)> poe.checkDocument('some other document').then(console.log) Promise { <pending> } true Эта версия лучше первой, но все еще содержит проблемы. Заметьте, что каждый раз, как мы хотим проверить, заверен ли документ, нам нужно проходить через все проверки. Их лучше сохранять в структуре, называемой «карта» (map). К счастью, язык Solidity поддерживает карты, называемые в нем mappings. Другая вещь, от которой мы избавимся в данной версии, это все те комментарии, которые маркируют read-only или транзакционные функции. Я думаю, теперь вы и сами можете в этом разобраться Code: // Proof of Existence contract, version 3 contract ProofOfExistence3 { mapping (bytes32 => bool) private proofs; // сохраним доказательство существования в состоянии контракта function storeProof(bytes32 proof) { proofs[proof] = true; } // вычислим и сохраним доказательство для документа function notarize(string document) { var proof = calculateProof(document); storeProof(proof); } // вспомогательная функция для вычисления sha256 function calculateProof(string document) constant returns (bytes32) { return sha256(document); } // проверка, был ли документ заверен function checkDocument(string document) constant returns (bool) { var proof = calculateProof(document); return hasProof(proof); } // возвращает 'true' если документ был заверен function hasProof(bytes32 proof) returns constant (bool) { return proofs[proof]; } } Такой код выглядит достаточно хорошо. И работает точно так, как работала вторая версия контракта. Чтобы его запустить, не забывайте обновлять файл миграции и запускать ‘truffle migrate —reset‘ снова. Весь код в данном руководстве может быть найден вот в этом репозитории GitHub. 5. Размещение контракта в реальной тестовой сети Как только вы протестировали ваш контракт, используя исключительно testrpc в симуляционной сети, вы готовы опробовать его уже и в реальной! Чтобы это сделать, нам нужен реальный testnet/livenet Ethereum клиент. Воспользуйтесь этими инструкциями по установке Geth. Во время разработки, вам нужно запускать узлы в testnet, так чтобы вы могли все протестировать, не рискуя при этом деньгами. Узел Testnet, также известный как Mordenв Эфириуме, в основном идентичен реальному Эфириуму, однако токен Ether, который там содержится, не имеет ценности. Не ленитесь, всегда помните, что нужно разрабатывать только в testnet, вы пожалеете, если потеряете настоящий Эфир из-за программной ошибки. Запустите Geth в Testnet, с включенным RPC сервером: Code: geth --testnet --rpc console 2>> geth.log Это действие открывает консоль, куда вы можете вбивать базовые команды для управления вашим узлом/клиентом. Ваш узел начнет загружать блокчейн Testnet, а вы можете отслеживать прогресс проверяя eth.BlockNumber. Пока загружается блокчейн, вы можете запускать команды. Например, давайте создадим счет: (хорошенько запомните свой пароль!) Code: > personal.newAccount() Passphrase: Repeat passphrase: "0xa88614166227d83c93f4c50be37150b9500d51fc" Давайте туда отправим немного монет и проверим баланс. Вы можете бесплатно получить Эфир Тестнета здесь, https://zerogox.com/ethereum/wei_faucet. Просто скопируйте и вставьте адрес, который вы только что сгенерировали, и вам пришлют 1 Ether для тестовой сети. Чтобы проверить свой баланс, наберите: Code: >eth.getBalance(eth.accounts[0]) 0 Эта команда не покажет никакого баланса, но это потому, что ваш узел еще не синхронизировался с остальной сетью. Пока вы этого ждете, проверьте свой баланс в блок эксплорере Testnet. Там, вы можете также посмотреть номер самого нового блока (#1355293 на момент написания). Вы можете использовать его в комбинации c eth.BlockNumber для вычисления момента полной синхронизации узла. Как только узел синхронизирован, вы будете готовы отправлять контракты используя Truffle. Сначала, разблокируйте свой главный geth аккаунт, чтобы Truffle мог его использовать. Также проверьте, что там имеются кое-какие средства, или вам не удастся запустить новый контракт в сеть. Code: > personal.unlockAccount(eth.accounts[0], "mypassword", 24*3600) true > eth.getBalance(eth.accounts[0]) 1000000000000000000 Готово! Если из этих двух не работает ни один, проверьте шаги, описанные выше, и убедитесь в правильности вашего кода. Теперь запускайте: Code: $ truffle migrate --reset Заметьте, что в этот раз контракт будет выполняться дольше, потому что мы соединяемся с реальной сетью, а не симуляционной из testrpc. Как только процесс закончен, вы можете взаимодействовать с контрактом точно так, как ранее. Отправленная в Testnet версия ProofOfEistence3 может быть найдена по адресу: https://testnet.etherscan.io/address/0xcaf216d1975f75ab3fed520e1e3325dac3e79e05 Я опущу детали относительно того, как размещать контракт на реальной сети. Вы должны делать это только после того, как в деталях проверили свои контракты в симуляционных и Test сетях. Помните, что любая программная ошибка может привести к потере реальных Эфиров! Безопасность контрактов в Эфириуме сложна «Смарт-контракты весьма сложно правильно настроить» Эмин Гюн Сирер Данное изначально, у смарт-контрактов есть свойство определять, как движутся компьютерные деньги, но я не могу закончить данное руководство не упомянув о безопасности. Я расскажу о безопасности смарт-контрактов подробнее в последующих статьях, но вот вам несколько быстрых заметок, чтобы начать. Проблемы, о которых стоит беспокоиться (и которых стоит избегать): Повторные вводы: Не производите внешние вызовы посредством контрактов. Если же вы это делаете, убедитесь без этого ну никак не обойтись. Отправка может провалиться: Когда вы отправляете деньги, ваш код всегда должен быть подготовлен к тому, что функция отправки может не сработать. Неопределенные циклы могут превысить лимит на топливо:будьте осторожны, когда вы выставляете цикл за рамками состояния переменных, которые могут сильно вырасти в размерах и достигнуть максимального лимита потребления топлива. Определяйте максимальную глубина списка вызовов: Не используйте рекурсию, и знайте, что любой вызов может провалиться, если достигнута предельная глубина списка (стека). Зависимость от временной метки: не используйте временные метки в критически важных местах кода, потому что майнеры могут ими манипулировать. Все это дается только в качестве примеров нестандартного поведения, которое может приводить к кражам или уничтожению средств в вашем смарт-контракте. Мораль проста: если вы пишете смарт-контракты, вы пишете код, который работает среальными деньгами. Вам нужно быть очень осторожным. Пишите тесты, пересматривайте код, и проводите аудит контрактов. Самый лучший способ избавиться от брешей в безопасности, это иметь цельное восприятие языка. Я рекомендую, чтобы выпочитали документацию к Solidity. Нам все еще нужны более совершенные инструменты для приемлемого уровня безопасности смарт-контрактов (прим. редактора: и это один из вопросов, поднимаемый разработчиками ETC) * * * Ну вот и все! Я надеюсь, вы насладились чтением этого руководства и вашим первым шагам в программировании смарт-контрактов на Эфириуме! Это все еще очень новая индустрия, и сейчас полно пространства для новых приложений и инструментов. Можете писать мне о ваших идеях и представлять прототипы. Авторы: Manuel Aráoz, BitCorps Источник:Medium.com Перевод взят отсюда: https://ethclassic.ru/2016/09/07/the-hitchhikers-guide-to-smart-contracts-in-ethereum/
Mastering Bitcoin на русском языке: http://smartcontracts.engineer/wp-content/uploads/2017/09/Mastering-Bitcoin.pdf