Создание смарт-контракта фабрики хранения в Solidity и взаимодействие с ним (заметки из Freecodecamp)

Я слежу за замечательным web3-курсом Freecodecamp на Youtube (ссылка здесь => https://www.youtube.com/watch?v=gyMwXuJrbJQ&ab_channel=freeCodeCamp.org), и чтобы убедиться, что я запомнил то, что узнал, я люблю делать заметки. Здесь я размещу свои заметки из Урока 3: Remix Storage Factory. Я сделаю свои беспорядочные заметки удобочитаемыми, чтобы они выглядели как учебник.

Пожалуйста, обратите внимание, что все заслуги принадлежат Патрику Коллинзу и невероятной команде/сообществу Freecodecamp, я просто передаю увиденное в письменном виде, чтобы в будущем мне не пришлось пересматривать учебник. Надеюсь, вы тоже сможете извлечь из него пользу.

Поскольку это 3-й урок, здесь отсутствуют некоторые основы. В нем также используется смарт-контракт SimpleStorage, представленный нам во 2-м уроке курса Freecodecamp. Возможно, вы захотите ознакомиться с ним, прежде чем читать этот пост, хотя я постараюсь объяснить, что происходит в контракте SimpleStorage. Если я в чем-то ошибусь, пожалуйста, не стесняйтесь поправить меня, так как прошло больше недели с тех пор, как я смотрел Урок 2, поэтому я мог забыть некоторые причины, по которым мы делаем все так, как делаем 🙂

Для этого поста мы будем использовать Remix IDE, поэтому убедитесь, что вы зашли на https://remix.ethereum.org/ и готовы к взлому!

Примечание: Вы можете найти код всех контрактов в репозитории Патрика на GitHub, здесь => https://github.com/PatrickAlphaC/storage-factory-fcc.

Простой смарт-контракт для хранения данных

Давайте начнем с рассмотрения смарт-контракта SimpleStorage.

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

    uint256 favoriteNumber;

    struct People {
        uint256 favoriteNumber;
        string name;
    }
    // uint256[] public anArray;
    //create an array of type People, which is a struct and name it people. It is also public.
    People[] public people;

    mapping(string => uint256) public nameToFavoriteNumber;

    function store(uint256 _favoriteNumber) public {
        favoriteNumber = _favoriteNumber;
    }

    function retrieve() public view returns (uint256){
        return favoriteNumber;
    }

    function addPerson(string memory _name, uint256 _favoriteNumber) public {
        people.push(People(_favoriteNumber, _name));
        nameToFavoriteNumber[_name] = _favoriteNumber;
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Все смарт-контракты Solidity начинаются с идентификатора SPDX-License-Identifier. Это лицензия, под которой работает смарт-контракт. В данном случае это MIT. Если я не ошибаюсь, это означает, что он с открытым исходным кодом.

Затем нам нужно определить версию компилятора Solidity. ^0.8.0 означает, что «все, что выше 0.8.0», подходит.

Затем мы используем ключевое слово contract, чтобы сообщить компилятору, что мы создаем смарт-контракт. Наш контракт называется SimpleStorage.

Поскольку Solidity является типизированным языком, нам нужно определить тип создаваемой переменной. Обычно я разработчик Javascript, поэтому мой ум работает в терминах JS. Вот как я это вижу: Вместо того чтобы сказать let favoriteNumber или const favoriteNumber, мы говорим uint256 favoriteNumber. Если бы мы определяли строку, мы бы сказали string favoriteNumber.

Теперь у нас готова переменная favoriteNumber, мы создаем структуру People. Структуры подобны объектам в Javascript. Они принимают пары ключ/значение. В данном случае это uint256 favoriteNumber и string name.

Затем мы создаем динамический массив people с типом People struct. Я еще не знаю Typescript или любой другой типизированный язык, кроме Solidity, поэтому эта часть была (и остается) довольно запутанной для меня. Типа, этот массив people может принимать в качестве значений только структуры People. Он не может принимать ни одну строку, ни массив строк, ни uints, ни что-либо другое, кроме структур People. Он не может принимать и другие типы структур, если я не ошибаюсь; он может принимать в качестве значений только и исключительно структуры People и все.

Также, используя этот синтаксис People[] public people, мы сообщаем компилятору, что это динамический массив структур People. То есть, длина массива не определена — потому что в Solidity это можно сделать, то есть иметь массивы с предопределенной длиной.

Еще одна особенность массива — здесь можно заметить ключевое слово public. Это означает, что данная переменная является публичной, т.е. ее можно увидеть и вызвать вне контракта. Если бы вместо этого мы сказали private, вы не смогли бы получить к ней доступ вне контракта.

Затем у нас есть отображение типа string на uint256. Сопоставления тоже сильно смущали меня в Solidity. Они похожи на объекты в JS, но вместо того, чтобы принимать несколько значений, как структуры, они принимают только одну пару ключ/значение и работают подобно массивам в Javascript. Они очень удобны, особенно при отображении адресов.

Наша первая функция, store — это публичная функция, которая принимает один параметр _favoriteNumber типа uint256 и изменяет переменную favoriteNumber на значение параметра _favoriteNumber. Подчеркивание (_) — это просто соглашение в Solidity, оно предназначено для параметров.

Далее, у нас есть функция retrieve, которая является публичной и view. Это означает, что ее можно увидеть и вызвать извне контракта. Она возвращает значение uint256 и НЕ стоит газа. Это потому, что она не изменяет состояние EVM (виртуальной машины Ethereum). Если нет изменений, то нет и затрат на газ.

Затем у нас есть функция addPerson, которая является публичной. Она принимает два параметра _name и _favoriteNumber типа string и uint256. Теперь, первая строка внутри этой функции, people.push(People(_favoriteNumber, _name));, делает следующее: она принимает параметры, создает из них структуру People и вставляет эту новую структуру People в массив people.

Вторая строка, nameToFavoriteNumber[_name] = _favoriteNumber;, делает следующее: она принимает параметры, создает с ними пару ключ/значение и помещает эту новую пару ключ/значение в связку nameToFavoriteNumber. В связке mapping(string => uint256) public nameToFavoriteNumber; у нас есть строка и uint256, поэтому _name идет как строка, а _favoriteNumber идет как uint256.

Вот и все для контракта SimpleStorage.sol. Вы можете вставить этот контракт, чтобы переделать, развернуть и поиграть с ним. Вы заметите, что пока мы можем получить только favoriteNumber, поскольку мы создали только функцию getter для этого.

Смарт-контракт StorageFactory

Для этой части мы создадим новый контракт в Remix IDE под названием StorageFactory.sol. Идея этой части заключается в том, чтобы создать новый контракт, который может создавать другие смарт-контракты. Да, смарт-контракты могут это делать. «Способность контрактов беспрепятственно взаимодействовать друг с другом известна как composability. «

Давайте проверим финальную версию смарт-контракта StorageFactory:


// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./SimpleStorage.sol";

contract StorageFactory {

    SimpleStorage[] public simpleStorageArray;

    function createSimpleStorageContract() public {
        SimpleStorage simpleStorage = new SimpleStorage();
        simpleStorageArray.push(simpleStorage);
    }

    function sfStore(uint256 _simpleStorageIndex, uint256 _simpleStorageNumber) public {

        simpleStorageArray[_simpleStorageIndex].store(_simpleStorageNumber);
    }

    function sfGet(uint256 _simpleStorageIndex) public view returns (uint256) {

        return simpleStorageArray[_simpleStorageIndex].retrieve();
    }
}
Войти в полноэкранный режим Выход из полноэкранного режима

Как вы видите, после нашей строки pragma solidity у нас есть этот оператор импорта => import "./SimpleStorage.sol";. Импорт работает как Javascript. Мы можем либо скопировать код, либо импортировать его таким образом, чтобы сделать его более управляемым.

Теперь, с помощью SimpleStorage[] public simpleStorageArray; мы создаем публичный массив с именем simpleStorage (minuscule) с типом контракта SimpleStorage (majuscule), который мы только что импортировали. Таким образом, этот массив будет содержать только и только контракты SimpleStorage. Круто, правда?

Публичная функция createSimpleStorageContract делает две вещи: В первой строке она создает переменную simpleStorage (minuscule) с типом контракта SimpleStorage (majuscule). Это делается с помощью ключевого слова new. Когда Solidity видит здесь ключевое слово ‘new’ в данном контексте, она говорит: «Ага! Мы собираемся развернуть новый контракт SimpleStorage». Во второй строке он помещает этот новый контракт в массив simpleStorageArray.

Функция sfStore («sf» означает «storageFactory») принимает два параметра uin256: индекс контракта, только что созданного и помещенного в массив, и любимый номер, который был в контракте simpleStorage.

Помните, что функция store, которая хранила любимый номер в simpleStorage.sol, была такой:

function store(uint256 _favoriteNumber) public{
favoriteNumber = _favoriteNumber;
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Затем строкой simpleStorageArray[_simpleStorageIndex].store(_simpleStorageNumber); она сохраняет любимое число, заданное в параметре uin256 _simpleStorageNumber, в simpleStorageArray по индексу, заданному в параметре _simpleStorageIndex. Это происходит путем вызова функции store в simpleStorage.sol, которую я показал выше.

Я знаю, что это звучит сложно, и, возможно, так оно и есть, просто мы пишем функцию, чтобы мы могли выбрать любой SimpleStorage, который мы создали с помощью функции createSimpleStorageContract, используя его индекс в массиве, чтобы мы присвоили ему любимый номер.

Следующая и последняя функция в этом контракте, sfGet («sf» снова означает «storageFactory»), является публичной функцией getter, и мы знаем, что она не стоит нам никакого газа, поскольку содержит ключевое слово view. Она принимает индекс контракта simpleStorage, который мы создали с помощью функции createSimpleStorageContract, и возвращает номер фаворита, который был в этом контракте, вызвав функцию retrieve в контракте simpleStorage.sol. Эта функция retrieve выглядела следующим образом:

//retrieve function in simpleStorage.sol
    function retrieve() public view returns (uint256){
        return favoriteNumber;
    }


Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь, скажем, если я открыл Remix IDE, скомпилировал и развернул контракт StorageFactory. sol, а затем вызвал функцию sfGet, создал кучу контрактов с помощью функции createSimpleStorageContract и, скажем, вызвал функцию sfStore с параметрами 0,22 и затем вызвал функцию sfGet с параметром 0, я бы получил 22 в качестве избранного числа. Если бы я вызвал функцию sfStore с параметрами 2,378 и затем вызвал функцию sfGet с параметром 2, я бы получил 378 в качестве любимого числа.

Вот и все. Теперь нам предстоит изучить еще одну вещь в этом посте — наследование.

Наследование

В учебнике Патрик показывает нам, как мы можем создать копию контракта и даже переопределить его тем или иным образом. Для этого нам нужно создать новый контракт под названием ExtraStorage.sol. Теперь, если мы импортируем контракт SimpleStorage.sol в этот новый ExtraStorage. sol и определим его как contract ExtraStorage is SimpleStorage вместо того, чтобы просто объявить contract ExtraStorage, как мы обычно делаем, этот новый ExtraStorage контракт будет иметь всю логику, переменные, функции и все, что есть в SimpleStorage контракте.

Но он идет еще дальше. Что если мы захотим изменить некоторые вещи в нашей копии SimpleStorage (которая является нашим контрактом ExtraStorage), и мы захотим добавить в него больше функциональности?

Тогда нам нужно переопределить его. Посмотрите этот фрагмент ExtraStorage.sol:

// SPDX-License-Identifier: MIT

pragma solidity 0.8.8;

import "./SimpleStorage.sol";

contract ExtraStorage is SimpleStorage {
    function store(uint256 _favoriteNumber) public override {
        favoriteNumber = _favoriteNumber + 5;
    }
}

Вход в полноэкранный режим Выход из полноэкранного режима

Видите, мы переопределяем функцию store в контракте SimpleStorage.sol. Это делается с помощью ключевого слова override. Мы просто добавляем цифру 5 к всеми любимому числу в эвристических целях.

Но это не может работать само по себе. Это не будет работать как переопределение. Нам нужен еще один дополнительный шаг, чтобы все работало без проблем. Нам нужно вернуться к нашему исходному контракту SimpleStorage.sol и добавить ключевое слово virtual к функции, которую мы хотим переопределить в дальнейшем. В данном случае это функция store в SimpleStorage.sol.

Итак, откроем контракт SimpleStorage.sol и изменим функцию store на эту:

    function store(uint256 _favoriteNumber) public virtual {
        favoriteNumber = _favoriteNumber;
    }

Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь эта функция store является «переопределяемой». Мы можем создать копию SimpleStorage.sol и переопределить его функцию store по своему усмотрению.

Вот и все, друзья! Надеюсь, вам понравился этот пост. Я надеюсь продолжить публикацию своих заметок, если все пойдет по плану. Тем не менее, я настоятельно рекомендую вам посмотреть оригинальный учебник на Youtube-канале Freecodecamp, он просто феноменален.

Сохраняйте спокойствие, & счастливого кодинга!

Edit: Вы можете найти заметки для Урока 4: Remix Fund Me здесь => https://dev.to/muratcanyuksel/creating-a-fundme-smart-contract-in-remix-ide-notes-from-freecodecamp-3imm

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