Solidity, смарт-контракты и IPFS


Солидность, контракты и IPFS

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

Мы также увидим реализацию dev-среды, затем реализацию контракта с Remix, затем мы подойдем к использованию IPFS для децентрализации ресурсов, на которые направлены наши токены.

Оглавление

  1. Напоминание о смарт-контрактах
  2. Solidity OOP для смарт-контракта
    • Среда разработки
      • Ganache-CLI
      • VSCode/Remix
      • Трюфель
      • Шутка/Чай
      • Метамаска
  3. Наш первый смарт-контракт ERC721
    • Токен-контракт
      • Контрактная основа и стандарт ERC721
      • Выполнение контракта НФТ с IPFS
    • Ценовая лента Chainlink для ценообразования в режиме реального времени
    • Создание минтера с ограничениями
      • Связывание шахты с ценовой лентой
    • Создание фабрики для развертывания
      • Развертывание testnet и тестирование с помощью Remix
  4. Управление ресурсами с помощью IPFS
    • Что такое IPFS?
    • Как это работает?
    • Роль IPFS при НФТ
    • Загрузка метаданных в IPFS
  5. Цель главы

Напоминание о смарт-контрактах

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

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

Этот тип контракта может применяться в нескольких областях:

  • Страхование
  • Недвижимость
  • Цепочки поставок (UPS, почта…)
  • Финансы

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

С помощью смарт-контракта можно «создать все», как на таком языке, как C#, но при использовании функции кода учитывается стоимость исполнения.

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

Solidity OOP для смарт-контрактов

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

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

Solidity доступна на блокчейнах:

  • Ethereum
  • Мята
  • Tron
  • Интеллектуальная цепочка Binance

Среда разработки

Блокчейн может показаться сложным и требует очень полной среды разработки, в нашем случае нам понадобится несколько программ/библиотек/IDE:

  • Ganache -> Позволяет локально моделировать узел ethereum
  • VSCode/Remix -> Локальная IDE и онлайн IDE
  • Truffle -> Среда разработки, тестовая среда и конвейер активов (js, css…).
  • Jest/Chai -> Библиотека тестов и утверждений для тестирования нашего контракта или DAPP
  • Metamask -> Криптовалютный кошелек для тестирования нашего локального блокчейна

Ganache-CLI

Ганаш, или крем-ганаш, … (ладно, я шучу)

Ganache — это инструмент, используемый для имитации локального блокчейна для быстрой разработки контрактов на блокчейне Ethereum:

  • Github Ganache
  • Документация по ганашу

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

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

VSCode/Remix

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

Для использования VSCode я советую вам установить расширение с синтаксисом Solidity в файлы .sol -> Solidity extension.

Remix — это онлайн IDE, которая позволяет разрабатывать, развертывать и администрировать смарт-контракт для блокчейн Ethereum.

Remix включает компилятор сценариев Solidity и тестовую сеть для развертывания контракта.

Remix также предоставляет доступ к интерфейсу, раскрывающему функции контракта, для тестирования этих функций.

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

Контракт, используемый для генерации этого NFT Fade, легко найти по его адресу

используя Blockscan, вы можете легко найти контракт, используемый для Fade
(Ссылка на контракт).

Таким образом, скопировав Fade.sol в Remix, я смогу получить доступ ко всем функциям контракта локально.
Таким образом, мы можем понять, что происходит за функцией tokenURI, которая каждый раз возвращает другой градиент.

Трюфель

Truffle — это среда разработки для Ethereum, цель которой — сделать обычно сложную разработку блокчейна доступной для всех.

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

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

Шутка/Чай

Jest — это фреймворк для тестирования JS, который позволит нам протестировать наш контракт и наш Dapp

Chai — это библиотека JS, которая позволит нам делать утверждения, то есть, следуя тесту, проверять возвращаемые значения.

Помните, что Jest и Chai рекомендуются при использовании Truffle и Ganache для выполнения автоматизированных тестов, а также для автоматического развертывания во время тестирования.

Метамаска

Metamask — это цифровой кошелек, он позволит нам хранить наши NFT или другие криптовалюты.

Metamask позволит управлять биржами на блокчейне, отображать свой баланс.

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

Наш первый смарт-контракт ERC721

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

Токен-контракт

Мы собираемся создать первый NFT Юнупа, для этого в Remix создайте новый файл с именем YounupNFT.sol.

Контрактная основа и стандарт ERC721

Мы начинаем с импорта стандартов, портированных OpenZeppelin, и полезных указаний компилятору:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
// Import of ERC721 Enumerable standard
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
// Import of Ownable standard
import "@openzeppelin/contracts/access/Ownable.sol";
// Import of Strings standard
import "@openzeppelin/contracts/utils/Strings.sol";
Войдите в полноэкранный режим Выход из полноэкранного режима

Реализация контракта НФТ с IPFS

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

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

contract YounupNFT is ERC721Enumerable, Ownable {
  //uint256 variable could use Strings library, example: value.toString()
  using Strings for uint256;
  //Declare metadata of token
  // ipfsURI => Internet Protocol File Storage URI
  string public ipfsURI;
  // ipfsExt => Extension like .json
  string public ipfsExt;
  constructor(string memory _name, string memory _symbol, string memory _ipfsURI) ERC721(_name, _symbol) {
    //Require is use to check the variable value, it will throw "No IPFS URI provided" if you don't pass it
    require(byte(_ipfsURI).length > 0, "No IPFS URI provided");
    // We allocate ipfsURI to the ipfsURI passed on contract deployment
    ipfsURI = _ipfsURI;
  }
  // This function return the metadata of a given token, here it will return our ipfsURI (json metadata)
  function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
    require(_exists(tokenId), "ERC721URIStorage: URI query for nonexistent token");
    return string(ipfsURI);
  }
  // This function is use to mint (obtain, buy...) a token, it will use _safeMint from ERC721 standard
  function mint(address recipient) public onlyOwner returns(uint256) {
    uint256 tokenId = this.totalSupply();
    _safeMint(recipient, tokenId);
    return tokenId;
  }
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Ценовая лента для получения цены в режиме реального времени

Мы хотим продавать наш NFT по фиксированной цене в $, поэтому мы будем использовать принцип цепной ценовой подачи

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

YounupNFT-priceFeed.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
//Import Chainlink aggregator to call datafeed with price
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract YounupNFTPriceFeed {
  // Private and immutable aggregator for security concern
  AggregatorV3Interface private immutable _aggregator;
  // We use feedAddress (address of the token) in the aggregator
  constructor(address feedAddress) {
    _aggregator = AggregatorV3Interface(feedAddress);
  }
  // Return last price of the token
  function getLatestPrice() external view returns (int) {
    // Only price not commented, we need to pass 5 arg, but we don't need the others datas
        (
          /*uint80 roundID*/,
          int price,
          /*uint startedAt*/,
          /*uint timeStamp*/,
          /*uint80 answeredInRound*/
        ) = _aggregator.latestRoundData();
        return price;
  }
  // Return the price decimals (since not all token have the same decimal we need to know it)
  function decimals() external view returns (uint8) {
    return _aggregator.decimals();
  }
}
// We add another contract so we could just deploy this one and don't need to send the token adress in the constructor since it's fixed here
// Here we pass the data from the MATIC testnet or mainnet because our NFT will be on polygon (MATIC)
contract PriceFeedMaticUSD is YounupNFTPriceFeed {
    /**
     * Network: Polygon
     * Aggregator: MATIC/USD
     * Address (mainnet): 0xAB594600376Ec9fD91F8e885dADF0CE036862dE0
     * Address (testnet): 0xd0D5e3DB44DE05E9F294BB0a3bEEaF030DE24Ada
     * From: https://docs.chain.link/docs/matic-addresses/
     */
  constructor () YounupNFTPriceFeed(0xd0D5e3DB44DE05E9F294BB0a3bEEaF030DE24Ada) {}
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Изготовление минтера с ограничениями

Для того чтобы применить ограничения на майнинг к нашему НФТ, например, ограничить его эмиссию или количество токенов, разрешенных для одного пользователя, мы создадим так называемый майнер.
Именно этот контракт будет единственным, который будет иметь право вызывать монетную функцию нашего контракта NFT через использование принципа Ownable, фактически наш контракт Minter будет развертывать контракт NFT и, следовательно, будет Владельцем.
Сначала мы определим структуру входных параметров контракта.

YounupNFT-minter.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
import "./YounupNFT-priceFeed.sol"
import "./YounupNFT.sol"
struct Parameters {
    uint8 chainCurrencyDecimals; // Decimal of the token use
    uint presalePriceUSD; // First sale price in USD
    uint publicsalePriceUSD; // Seconde sale price in USD
    uint maxSupply; // Max token mintable
    uint maxMint; // Max mint by user
    Token token; // Token is the YounupNFT token
    YouNFTupPriceFeed priceFeed; // Our created pricefeed contract
    Periods periods; // We will use periods to know when it's first or second sales
    address payable teamAddress; // This is the team wallet that will receive all funds
}
struct Token {
  string name; // Token name ex: Ethereum
  string symbol; // Token symbol ex: ETH
  string ipfsURI; // Token IPFS Uri
}
struct Periods {
  // timestamp use for start and end private sale
  uint startPresalePeriod;
  uint endPresalePeriod;
  // timestamp use for start and end of public sale
  uint startPublicsalePeriod;
  uint endPublicsalePeriod;
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Теперь, когда мы определили структуры, которые позволят нам использовать и защитить наш контракт, мы можем написать все остальное:

contract YounupNFTMinter {
  // We use it so we could now at wich state we are
  enum State
  {
    waitingPeriod, // no mint possible
    presalePeriod, // Mint with the private sale price
    publicsalePeriod, // Mint with the public sale price
    complete // All items are minted no more mint possible or you have outdated the endPublicSalePeriod
  }
   // Chain info and team wallet
  uint8 public immutable chainTokenDecimals;
  address payable public immutable teamAddress;
  YounupNFT public immutable token;
  //NFT max supply
  uint public immutable maxSupply;
  //NFT max mint per wallet
  uint public immutable maxMint;
  // NFT price in USD
  uint public immutable presalePriceUSD;
  uint public immutable publicsalePriceUSD;
  uint constant ticketPriceDecimals = 2;
  //Periods
  Periods public periods;
  //Chainlink Price feed
  YounupNFTPriceFeed public immutable priceFeed;
   constructor(Parameters memory params) {
     // Many test use to ensure that the periode are logics
      require(params.periods.startPresalePeriod > block.timestamp, "Invalid timestamp: startPresalePeriod");
      require(params.periods.endPresalePeriod > params.periods.startPresalePeriod, "Invalid timestamp: endPresalePeriod");
      require(params.periods.startPublicsalePeriod > params.periods.endPresalePeriod, "Invalid timestamp: startPublicsalePeriod");
      require(params.periods.endPublicsalePeriod > params.periods.startPublicsalePeriod, "Invalid timestamp: endPlubicsalePeriod");
      // We create the contract for the Token here
      token = new YounupNFT(this, params.token.name, params.token.symbol, params.token.ipfsURI);
      chainTokenDecimals = params.chainCurrencyDecimals;
      presalePriceUSD = params.presalePriceUSD;
      publicsalePriceUSD = params.publicsalePriceUSD;
      maxSupply = params.maxSupply;
      maxMint = params.maxMint;
      priceFeed = params.priceFeed;
      periods = params.periods;
      teamAddress = params.teamAddress;
    }
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Связывание шахты с ценовой лентой

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

  function mintNFT() external payable {
    // Check if contract is in state where you could mint
    require(getState() == State.presalePeriod || getState() == State.publicsalePeriod, "You can't mint it right now");
    // Get the mint price from priceFeed and ensure everything is ok with all limits and price passed
    uint entryPrice = getMintPrice();
    require(msg.value >= entryPrice, "Not enough funds to mint YouNFTup. See getMintPrice()");
    require(token.totalSupply() < maxSupply, "Too much NFT minted");
    require(token.balanceOf(msg.sender) < maxMint, "You could mint only 1 YouNFTup");
    // Mint the token using the YounupNFT mint function
    token.mint(msg.sender);
    // We also transfer all the fund sent to the contract to the team wallet
    teamAddress.transfer(address(this).balance);
  }
  // Function use to get the current state of the contract
  function getState() public view returns(State) {
    if (block.timestamp < periods.startPresalePeriod) {
        return State.waitingPeriod;
    }
    if (block.timestamp < periods.endPresalePeriod) {
        return State.presalePeriod;
    }
    if (block.timestamp < periods.startPublicsalePeriod) {
        return State.waitingPeriod;
    }
    if (block.timestamp < periods.endPublicsalePeriod) {
        return State.publicsalePeriod;
    }
    return State.complete;
  }
  // Function use to get the mint price using priceFeed.getLatestPrice()
  function getMintPrice() public view returns(uint) {
    int latestPrice = priceFeed.getLatestPrice();
    uint256 ticketPriceUSD = publicsalePriceUSD;
    if (getState() == State.presalePeriod) {
        ticketPriceUSD = presalePriceUSD;
    }
    uint latestPriceAdjusted = uint(latestPrice) * 10 ** (chainTokenDecimals - priceFeed.decimals());
    uint entryPrice = 10 ** (chainTokenDecimals * 2) * ticketPriceUSD / latestPriceAdjusted / (10 ** ticketPriceDecimals);
    return entryPrice;
  }
  // Function to check if the contract is complete or no
  function isComplete() public view returns(bool) {
    return getState() == State.complete;
  }
Войдите в полноэкранный режим Выход из полноэкранного режима

Нам также необходимо обновить токен, поскольку теперь мы используем майнер, как показано в следующей строке:

это эквивалентно YounupNFTMinter

YounupNFT.sol

import "./YounupNFTMinter.sol"
...
constructor(YouNFTupMinter _minter, ...) {
  // We keep info about the minter contract address
  minter = _minter;
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Функция майнинга является public onlyOwner только контракт майнера сможет майнить токен YounupNFT

Создание фабрики для развертывания

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

YounupNFT-factory.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
import "./YouNFTup-minter.sol";
contract YouNFTupFactory is YouNFTupMinter {
  constructor() YouNFTupMinter(Parameters({
      chainCurrencyDecimals: 18,
      presalePriceUSD: 100,
      publicsalePriceUSD: 200,
      maxSupply: 42,
      maxMint: 1,
      token: Token({
          name: "YouNFTup",
          symbol: "YNP",
          ipfsURI: "https://ipfs.io/ipfs/bafkreihmkaetjsqkb2cnzdkyhvpattzpj7duuqj4kfvtx3qfcitj2didbu"
      }),
      priceFeed: new PriceFeedMaticUSD(),
      periods: Periods({
          startPresalePeriod: block.timestamp + 1,
          endPresalePeriod: block.timestamp + 3600 * 24 * 30,
          startPublicsalePeriod: block.timestamp + 3600 * 24 * 60,
          endPublicsalePeriod: block.timestamp + 3600 * 24 * 90
      }),
      teamAddress: payable(0xB677dd9Ae9217Fbb4E3d072b9F7F68947C2a4AA6)
  })) {}
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Развертывание testnet и тестирование с помощью Remix

Осталось только составить наш контракт и развернуть его в тестовой сети Matic.
(чтобы добавить сеть, она находится здесь)

В части remix «Solidity Compiler» нужно выбрать версию компилятора 0.8.12, затем скомпилировать контракт YounupNFT-factory.sol, который импортирует все остальные.

Если вы установили Metamask и добавили сеть Matic Mumbai, то теперь мы развернем наш контракт.

В части Remix «Deploy & run transactions» вы должны выбрать для Environment «Injected Web3», после чего у вас появится уведомление Metamask, не забудьте проверить, что вы находитесь в тестовой сети полигонов перед развертыванием.

Затем выберите контракт YounupNFT-factory и нажмите на «Deploy», транзакция появится на вашем Metamask, если вы примете ее, вы заплатите за развертывание контракта (в testnet это бесплатно).

Затем вы получите доступ ко всем функциям вашего контракта, чтобы иметь возможность протестировать его, вы сможете, наконец, добыть свой первый NFT и проверить его существование через OpenSea Testnet

Управление ресурсами с помощью IPFS

Что такое IPFS?

IPFS или InterPlanetary File System — это протокол P2P (peer to peer) Web3.0.
В мире Web 2.0 мы храним наши файлы на централизованных серверах, здесь же идея состоит в том, чтобы полагаться на децентрализацию и сеть совместной работы для размещения файлов.

Как это работает?

Цель IPFS — сделать сеть :

  • быстрее
  • более безопасный
  • более открытый
  • менее дорогой

В идеале децентрализации он стремится занять значительное место перед протоколом HTTP

Роль IPFS при НФТ

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

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

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

Загрузка метаданных в IPFS

Для того, чтобы получить ipfs URL, который позволит нам получить информацию из нашего NFT в контракте.

Начиная с того, что мы будем развивать здесь:

      token: Token({
          name: "YouNFTup",
          symbol: "YNP",
          ipfsURI: "https://ipfs.io/ipfs/bafkreihmkaetjsqkb2cnzdkyhvpattzpj7duuqj4kfvtx3qfcitj2didbu"
      }),
Войдите в полноэкранный режим Выход из полноэкранного режима

вся задача может быть автоматизирована с помощью сценариев
Для этого мы будем использовать nft.storage, есть также pinata :

  • Создайте учетную запись или войдите в систему с помощью github
  • Загрузите свое изображение, чтобы получить ссылку на IPFS
  • Запишите файл metadata.json.
{
  "description": "Official Younup NFT",
  "external_url": "https://younup.fr",
  "image": "ipfs://your-image-link", //example: https://ipfs.io/ipfs/bafkreigf2d4qgt6klvuxwr7d3yvkpjlrszlfhgpbphbdvwkzljivef24iu
  "name": "YouNFTup",
  "attributes": [] // You can add many attributes check OpenSea doc
}
Войдите в полноэкранный режим Выход из полноэкранного режима
  • Загрузите файл метаданных

Наконец, вы получите файл metada, который вы можете связать с вашим NFT.

Пример: https://ipfs.io/ipfs/bafkreihmkaetjsqkb2cnzdkyhvpattzpj7duuqj4kfvtx3qfcitj2didbu

Цель главы

Код: https://github.com/SoxZz5/NFT-tuto/tree/master/project/contracts

Résultat: https://testnets.opensea.io/assets/mumbai/0xe839ceaa7d410c7e957a97d970b8042b13a21c28/0

Контракт с Минтером: https://mumbai.polygonscan.com/address/0xc7cdcdfa7c724bd7148efb53a5774928f56dab46

NFT contract: https://mumbai.polygonscan.com/address/0xe839ceaa7d410c7e957a97d970b8042b13a21c28

Оцените статью
Procodings.ru
Добавить комментарий