April 17, 2022

Учебник по написанию смарт-контракта

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

Если оформление статьи вам кажется ужасным, то включите белую тему в настройках Телетайпа

Ресурсы для новичков:

1. НАСТРОЙКА ОКРУЖЕНИЯ

🦊 Метамаск

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

Разумеется никто не будет тратить настоящие деньги для простой проверки минта или апрува абстрактного NFT, поэтому были созданы тестовые сети, которые дают вам определенное количество "расходного материала"

Rinkeby — дает тестовый ETH для работы в его же тестовой сети. Есть ограничения на получение и требуется привязать социальные сети (Твиттер или Фейсбук) чтобы никто слишком не абузил кран

Перед началом нам надо добавить тестовые сети в нашем кошельке Метамаск

🚿 Получаем тестовый ETH

1) Создаем кошелек Метамаск (можете использовать нынешний)

2) Открываем виджет Метамаска, нажимаем на кружочек идентикона. Снизу заходим в "Настройки"

3) Во вкладке "Дополнительно" активируем кнопку "Показывать тестовые сети"

4) Теперь мы можем выбрать тестовую сеть Rinkeby и перейти на сайт, где получим ETH

💻 Создание директории и установка пакетов

Следующие команды будут выполнятся на системе под Линуксом. Если вы не умеете пользоваться командной строкой, то пройдите этот курс

1) Установим NPM

sudo apt install npm

2) Создаем директорию для проекта

mkdir nft-tutorial
cd nft-tutorial

3) Инициализируем проект и заполняем описание и прочее. Можете просто прокликать Enter

npm init

4) Устанавливаем зависимости

npm install --save-dev hardhat
...

npm install --save-dev @nomiclabs/hardhat-ethers
...

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

npm install @openzeppelin/contracts
...

npm install dotenv --save
...

npm install --save-dev ethers@^5.0.0
...

npm install --save-dev node-fetch@2
...

5) После всей установки у вас будет такое содержание файла (если чуть отличается - ничего страшного)

{
  "name": "asynco",
  "version": "1.0.0",
  "description": "Test",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Asynco",
  "license": "ISC",
  "devDependencies": {
    "@nomiclabs/hardhat-ethers": "^2.0.2",
    "ethers": "^5.5.2",
    "hardhat": "^2.7.0",
    "node-fetch": "^2.6.6"
  },
  "dependencies": {
    "@openzeppelin/contracts": "^4.4.0",
    "dotenv": "^10.0.0"
  }
}

👷‍♂️ Инициализация жесткого диска

Теперь нам надо донастроить жесткий диск при помощи Hardhat.

1) Следуйте инструкции:

> npx hardhat
888    888                      888 888               888
888    888                      888 888               888
888    888                      888 888               888
8888888888  8888b.  888d888 .d88888 88888b.   8888b.  888888
888    888     "88b 888P"  d88" 888 888 "88b     "88b 888
888    888 .d888888 888    888  888 888  888 .d888888 888
888    888 888  888 888    Y88b 888 888  888 888  888 Y88b.
888    888 "Y888888 888     "Y88888 888  888 "Y888888  "Y888

👷 Welcome to Hardhat v2.7.0 👷‍

? What do you want to do? …
  Create a basic sample project
  Create an advanced sample project
  Create an advanced sample project that uses TypeScript
❯ Create an empty hardhat.config.js
  Quit
  
  ...
  
  ✨ Config file created ✨

2) В конце у нас будет такая структура файла:

> tree -L 3 -I 'node_modules*|cache*'
.
├── hardhat.config.js
├── package-lock.json
└── package.json
  • hardhat.config.js это файл конфигурации Hardhat, в котором мы можем определить наши переменные блокчейна и учетные записи для использования
  • package.json и package-lock.json это файлы созданные npm. Используются для определения зависимостей, которые нам могут понадобиться

2. БАЗОВЫЙ ПРОЕКТ

🔗 Структура каталогов

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

1) Создаем папки:

mkdir contracts
mkdir scripts
  • contracts/ весь код Solidity тут
  • script/ файлы JS для последующего развертывания контракта

📃 Шаблон контракта

Заходим в нашу папку contracts/ и создаем там первый файл NFT.sol

1) Записываем шаблон:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract NFT is ERC721 {
    using Counters for Counters.Counter;
    Counters.Counter private currentTokenId;
    
    constructor() ERC721("NFTTutorial", "NFT") {}
    
    function mintTo(address recipient)
        public
        returns (uint256)
    {
        currentTokenId.increment();
        uint256 newItemId = currentTokenId.current();
        _safeMint(recipient, newItemId);
        return newItemId;
    }
}

Теперь разберемся, что тут написано:

  • в 1 строке мы определяем версию Солидити;
  • импортируем заготовку для контракта:
    • ERC721.sol: "ванильная" реализация для NFT уже реализует массу полезных вспомогательных функций. Для получения дополнительной информации обратитесь к документации OpenZeppelin;
    • Counters.sol: предоставляет счетчики, которые мы можем использовать для отслеживания общего количества отчеканенных токенов;
  • следующие несколько строк определяют сам контракт NFT;
  • строка 7 определяет NFT для наследования от ERC721. Обратите внимание, что контракты Solidity поддерживают использование миксинов и могут наследовать сразу несколько разных контрактов;
  • Строки 8-9 импортируют, а затем объявляют счетчик, который мы будем использовать для эффективного отслеживания общего количества токенов, отчеканенных в нашем контракте;
  • Строка 11 определяет конструктор, который на данный момент просто вызывает свой родительский конструктор ERC721 и передает две строки: name и symbol. На данный момент этот конструктор довольно легкий;
  • Наконец, мы определяем mintTo функцию. Эта публичная функция может быть вызвана путем передачи действительного адреса получателя для чеканки нового NFT. На данный момент это очень просто:
    • Это увеличивает наш currentTokenId счетчик;
    • Минтит следующее значение счетчика recipient, используя _safeMint() (частный метод OpenZeppelin);
    • Наконец, он возвращает идентификатор новоиспеченного токена обратно вызывающему адресу.

🎉 Поздравляю!

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

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

Благодаря стандартизированной реализации OpenZeppelin, контракты получаются простые и красивые

После всех манипуляций мы получаем следующую структуру нашего проекта:

tree -L 3 -I 'node_modules*|cache*'

├── contracts 
│ └── NFT.sol 
├── hardhat.config.js 
├── package-lock.json 
├── package.json 
└── scripts

🔩 Компиляция контракта

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

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

1) Давайте создадим новый файл, .env в корневой папке проекта. Мы будем использовать этот файл для хранения закрытого ключа нашей учетной записи, а также нашего API-ключа Alchemy.

  • Получите свой закрытый ключ из кошелька MetaMask, следуя инструкциям здесь
  • Получите свой API-ключ Alchemy здесь

2) После добавим в созданный выше файл наши данные по следующей структуре:

ALCHEMY_KEY = "alchemy-api-key"
ACCOUNT_PRIVATE_KEY = "private-key"

3) Затем обновите hardhat.config.js файл, добавив следующее:

/**
* @type import('hardhat/config').HardhatUserConfig
*/

require('dotenv').config();
require("@nomiclabs/hardhat-ethers");

const { ALCHEMY_KEY, ACCOUNT_PRIVATE_KEY } = process.env;

module.exports = {
   solidity: "0.8.0",
   defaultNetwork: "rinkeby",
   networks: {
    hardhat: {},
    rinkeby: {
      url: `https://eth-rinkeby.alchemyapi.io/v2/${ALCHEMY_KEY}`,
      accounts: [`0x${ACCOUNT_PRIVATE_KEY}`]
    },
    ethereum: {
      chainId: 1,
      url: `https://eth-mainnet.alchemyapi.io/v2/${ALCHEMY_KEY}`,
      accounts: [`0x${ACCOUNT_PRIVATE_KEY}`]
    },
  },
}

4) Компилируем контракт!

npx hardhat compile

Мы должны получить следующий ответ:

Compiling 1 file with 0.8.0
Compilation finished successfully

Если все хорошо, то ошибок

📚 Развертывание контракта

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

1) Внутри scripts/создайте файл с именем deploy.js и заполните его таким кодом:

async function main() {

    // Get our account (as deployer) to verify that a minimum wallet balance is available
    const [deployer] = await ethers.getSigners();

    console.log(`Deploying contracts with the account: ${deployer.address}`);
    console.log(`Account balance: ${(await deployer.getBalance()).toString()}`);

    // Fetch the compiled contract using ethers.js
    const NFT = await ethers.getContractFactory("NFT");
    // calling deploy() will return an async Promise that we can await on 
    const nft = await NFT.deploy();

    console.log(`Contract deployed to address: ${nft.address}`);
}

main()
.then(() => process.exit(0))
.catch((error) => {
    console.error(error);
    process.exit(1);
});
  • Эта функция JS извлекает скомпилированный контрактNFT.solи развертывает его. Он также распечатывает баланс вашего кошелька до и после развертывания, так как в транзакции будет использоваться газ.
  • Большая часть этого кода скопирована из Hardhat, которые отлично справляются с объяснением различных компонентов в их учебнике. Не позволяйте названию раздела обмануть вас, он полон полезной информации Hardhat, связанной не только с тестированием.

2) Теперь наш проект должен выглядеть так:

> tree -L 3 -I 'node_modules*|cache*'

├── artifacts 
│ ├── @openzeppelin 
│ │ └── contracts 
│ ├── build-info 
│ │ └── f179c78b6322d2fddc1e72511467aa46.json 
│ └── contracts 
│ └── NFT.sol 
├── contracts 
│ └── NFT.sol 
├── hardhat.config.js 
├── package-lock.json 
├── package.json 
└── scripts  
└── deploy.js 

8 directories, 6 files

Примечание: artifactsr содержит все наши скомпилированные контракты и их зависимости

3) Развертываем!

npx hardhat run scripts/deploy.js --network rinkeby

и получаем ошибку такой ответ:

Deploying contracts with the account: {адрес контракта}
Account balance: 505059205368653101
Contract deployed to address: {адрес NFT контракта}

🛂 Проверяем контракт

Из ответа выше берем адрес развернутого контракта и смотрим в Etherscan Rinkeby результаты! Ура, мы программисты!

📘 Майнет

Поскольку мы потратили время , чтобы определить как тестовую сеть Rinkeby, так и основную сеть Ethereum в качестве сетевых опций hardhat.config.js, развертывание одного и того же кода контракта в основной сети (когда мы будем готовы) так же просто, как замена --network rinkeby--network ethereum в приведенной выше команде =)

3. Итоги

  • Мы написали базовый смарт-контракт в Solidity, который использует существующую ERC721 реализацию
  • Мы создали базовый конфигурационный файл Hardhat и скомпилировали наш первый контракт
  • Наконец, мы написали базовый скрипт для развертывания нашего контракта на JavaScript и фактически развернули его в Rinkeby блокчейне

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

✅ В следующей части рассмотрим:

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

Канал - https://t.me/in_crypto_Info

Дропы - https://t.me/in_airdrop_crypto

Чат - https://t.me/crypto_chat_rus