Я слежу за замечательным 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