April 17

Квесты Base

Введение

Base запустили обучающую кампанию Base Camp. Необходимо проходить обучение по Solidity, создавать смарт-контракты и получать NFT. За эти NFT можно забрать роли в Guild.

Есть шанс, что эти NFT и роли могут стать мультипликатором для дропа.

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

Вся активность происходит в тестовой сети Base Sepolia, так что никаких затрат и опасности тут нет.

Всего надо выполнить 13 тасков. Ссылки на все таски тут👇

https://docs.base.org/base-camp/docs/exercise-contracts

Приступаем:

Подготовка и необходимые ресурсы

  1. Добавляем сеть Base Sepolia в метамаск. Лучше всего официальную RPC, как на фото

👉🏽 https://chainlist.org/chain/84532

  1. Пополняем сеть Base Sepolia тестовыми эфирами.

Можно запросить из крана тут:

👉🏽 https://www.alchemy.com/faucets/base-sepolia

Или забриджить из сети ETH Sepolia через мост. Достаточно 0.2 ETH

👉🏽 https://sepolia-bridge.base.org/deposit

3. Заходим на страницу с бэйджами. Здесь будем отслеживать прогресс. По мере получения NFT, они будут отражаться синим цветом.

https://docs.base.org/base-camp/progress/

4. Заходим в дискорд Base, если еще не присоединились.

👉🏽 https://discord.com/invite/buildonbase

5. После получения всех NFT заходим в гильдию и забираем роли. В дискорде у вас будут отображаться новые роли

👉🏽 https://guild.xyz/base/base-camp

Remix

Remix - онлайн сервис, через который все желающие могут деплоить контракты. Ничего устанавливать не надо. Просто заходите на сайт и подготовим все что нужно по шагам.

  • Заходим на сайт и видим такой интерфейс.

https://remix.ethereum.org/

Создаем свое рабочее пространство

  • Выбираем General - Basic

Template - Basic Workspace name - любое (в примере будет “BASE”) OK

Видим нашу папку и удаляем все кроме 1_Storage.sol. На фото все, что выделено красным - удаляем:

Остается так:

Чтобы создать новый файл нажимаем кнопку как на фото и пишем имя файла и жмем Enter

Пишите имя файла с названием квеста, чтобы не путаться потом

Для каждого таска надо создавать новый файл

name.sol

  • Нажимаем на имя файла 1_Storage.sol и справа увидим код контракта. В этом окне можно писать и менять код.
  • Именно сюда необходимо будет вставлять код, который мы собрали в гайде

Заходим на вкладку Solidity compiler и выставляем настройки как на фото.

Это сделаем 1 раз и больше не трогаем

Заходим на вкладку Deploy & Run Transactions. На этой вкладке мы будем деплоить все наши контракты.

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

1. Вкладка Deploy & Run Transactions

2. ENVIRONMENT - Выбираем среду для разворачивания контракта. Ставим Injected Provider - это наш Метамаск. Он сразу откроется и попросит подключиться. Подключаем его.

3. ACCOUNT - Проверяем, чтоб отображался наш адрес Метамаска

4. CONTRACT - В этом окне выбирается контракт, который будем деплоить. Сейчас это 1_Storage

5. DEPLOY - кнопка, которую нажимаем, чтобы задеплоить контракт

‼️ Очень важно следить, что в кошелке у вас именно сеть BASE SEPOLIA. Потому что, Remix’у все равно в какой сети деплоить и если у вас выбрана сеть ETHEREUM, то будет деплой в ней и заплатите баксов 40 комиссии!

  • Если все сделали правильно, то ниже появится такая вкладка нашего контракта, где мы можем скопировать адрес нашего контракта. Именно этот адрес мы будем вставлять на сайте Base для проверки и получения NFT

Как работать с гайдом дальше?

‼️ Теперь у нас есть все, чтобы разворачивать контракты и забрать NFT и роли:

  1. Создаете новый файл
  2. Вставляете текст контракта из гайда
  3. Деплоите контракт
  4. Копируете адрес вашего контракта
  5. Вставляете адрес контракта по указанной ссылке и подписываете тестовую транзакцию
  6. ЗДЕСЬ проверяете, что получили бэйдж

Далее для ускорения процесса для каждого бэйджа будет указано только 2 пункта: текст контракта, ссылка на верификацию контракта.

В некоторых задания нужны будут дополнительные действия, все будет указано.

Погнали!

Ёще один очень важный момент! Если все будут тупо копировать и деплоить один и тот же контракт, то байткод контрактов будет одинаковый и это может быть палевом для проекта!

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

string private salt = "ваша строка тут";

То, что выделено красным меняете на любую абракадабру (Латинские буквы и цифры) в каждом контракте и все. Remix все равно не даст вам деплоить с русским буквами, так что вы не забудете это.

Деплоим контракты

1. Deployment Exercise

Contract:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract BasicMath {

string private salt = "ваша строка тут";

function adder(uint _a, uint _b) external pure returns (uint sum, bool error) {

unchecked {

uint result = _a + _b;

if (result >= _a && result >= _b) {

return (result, false);

} else {

return (0, true);

}

}

}

function subtractor(uint _a, uint _b) external pure returns (uint difference, bool error) {

unchecked {

if (_b <= _a) {

return (_a - _b, false);

} else {

return (0, true);

}

}

}

}

2. Control Structures Exercise

Contract:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract ControlStructures {

error AfterHours(uint256 _time);

string private salt = "ваша строка тут";

// Smart Contract FizzBuzz

function fizzBuzz(uint256 _number) public pure returns (string memory) {

if (_number % 3 == 0 && _number % 5 == 0) {

return "FizzBuzz";

} else if (_number % 3 == 0) {

return "Fizz";

} else if (_number % 5 == 0) {

return "Buzz";

} else {

return "Splat";

}

}

// Do Not Disturb

function doNotDisturb(uint256 _time) public pure returns (string memory) {

if (_time >= 2400) {

revert("1");

} else if (_time >= 2200 || _time < 800) {

revert AfterHours(_time);

} else if (_time >= 1200 && _time <= 1259) {

revert("At lunch!");

} else if (_time >= 800 && _time <= 1199) {

return "Morning!";

} else if (_time >= 1300 && _time <= 1799) {

return "Afternoon!";

} else {

return "Evening!";

}

}

}

Тут будет несколько шагов:

  1. Копируете как обычно контракт из гайда👇

Contract:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract EmployeeStorage {

uint16 private shares;

uint24 private salary;

uint public idNumber;

string public name;

error TooManyShares(uint16 totalShares);

constructor(

uint16 _shares,

string memory _name,

uint24 _salary,

uint _idNumber

) {

shares = _shares;

name = _name;

salary = _salary;

idNumber = _idNumber;

}

function viewSalary() public view returns (uint24) {

return salary;

}

function viewShares() public view returns (uint16) {

string memory salt = "ваша строка тут";

return shares;

}

function grantShares(uint16 _newShares)  external  {

uint16 totalShares = shares + _newShares;

require(_newShares < 5000, "Too many shares");

if (totalShares > 5000) {

revert TooManyShares(totalShares);

}

shares = totalShares;

}

/**

* Do not modify this function.  It is used to enable the unit test for this pin

* to check whether or not you have configured your storage variables to make

* use of packing.

*

* If you wish to cheat, simply modify this function to always return `0`

* I'm not your boss ¯\\_(ツ)_/¯

*

* Fair warning though, if you do cheat, it will be on the blockchain having been

* deployed by your wallet....FOREVER!

*/

function checkForPacking(uint _slot) public view returns (uint r) {

assembly {

r := sload (_slot)

}

}

/**

* Warning: Anyone can use this function at any time!

*/

function debugResetShares() public {

shares = 1000;

}

}

В этом контракте надо найти и изменить строчку

string memory salt = "ваша строка тут";

То что выделено красным меняем на любые символы-цифры. 3. Идем на вкладку Deploy(1) и заполняем поля как на скриншоте (2), затем жмем transact (3).

  • shares - 1000
  • name - Pat
  • salary - 50000
  • idNumber - 112358132134

Подписываем транзакцию и контракт задеплоен. Дальше по обычной схеме.

https://docs.base.org/base-camp/docs/storage/storage-exercise/

4. Arrays Exercise

Contract

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract ArraysExercise {

**string private salt = "ваша строка тут";**

uint[] public numbers = [1,2,3,4,5,6,7,8,9,10];

address[] public senders;

uint[] public timestamps;

function getNumbers() public view returns (uint[] memory) {

return numbers;

}

function resetNumbers() public {

delete numbers;

numbers = [1,2,3,4,5,6,7,8,9,10];

}

function appendToNumbers(uint[] calldata _toAppend) public {

uint numbersLength = numbers.length;

uint _toAppendLength = _toAppend.length;

uint newLength = numbersLength + _toAppendLength;

uint[] memory newNumbers = new uint[](newLength);

for (uint i = 0; i < numbersLength; i++) {

newNumbers[i] = numbers[i];

}

for (uint j = 0; j < _toAppendLength; j++) {

newNumbers[numbersLength + j] = _toAppend[j];

}

numbers = newNumbers;

}

function saveTimestamp(uint _unixTimestamp) public {

senders.push(msg.sender);

timestamps.push(_unixTimestamp);

}

function afterY2K() public view returns (uint[] memory, address[] memory) {

uint[] memory recentTimestamps = new uint[](timestamps.length);

address[] memory correspondingSenders = new address[](timestamps.length);

uint y2kTimestamp = 946702800;

uint count = 0;

for (uint i = 0; i < timestamps.length; i++) {

if (timestamps[i] > y2kTimestamp) {

recentTimestamps[count] = timestamps[i];

correspondingSenders[count] = senders[i];

count++;

}

}

// Resize arrays to fit only the actual values

uint[] memory trimmedRecentTimestamps = new uint[](count);

address[] memory trimmedCorrespondingSenders = new address[](count);

for (uint j = 0; j < count; j++) {

trimmedRecentTimestamps[j] = recentTimestamps[j];

trimmedCorrespondingSenders[j] = correspondingSenders[j];

}

return (trimmedRecentTimestamps, trimmedCorrespondingSenders);

}

function resetSenders() public {

delete senders;

}

function resetTimestamps() public {

delete timestamps;

}

}

Contract:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract FavoriteRecords {

string private salt = "ваша строка тут";

mapping(string => bool) public approvedRecords;

mapping(address => mapping(string => bool)) public userFavorites;

constructor() {

approvedRecords["Thriller"] = true;

approvedRecords["Back in Black"] = true;

approvedRecords["The Bodyguard"] = true;

approvedRecords["The Dark Side of the Moon"] = true;

approvedRecords["Their Greatest Hits (1971-1975)"] = true;

approvedRecords["Hotel California"] = true;

approvedRecords["Come On Over"] = true;

approvedRecords["Rumours"] = true;

approvedRecords["Saturday Night Fever"] = true;

}

function getApprovedRecords() public view returns (string[] memory) {

string[] memory records = new string[](9);

for (uint i = 0; i < 9; i++) {

records[i] = getApprovedRecordAtIndex(i);

}

return records;

}

function getApprovedRecordAtIndex(uint index) private view returns (string memory) {

require(index < 9, "Index out of range");

if (index == 0) return "Thriller";

if (index == 1) return "Back in Black";

if (index == 2) return "The Bodyguard";

if (index == 3) return "The Dark Side of the Moon";

if (index == 4) return "Their Greatest Hits (1971-1975)";

if (index == 5) return "Hotel California";

if (index == 6) return "Come On Over";

if (index == 7) return "Rumours";

if (index == 8) return "Saturday Night Fever";

}

function addRecord(string memory _albumName) public {

require(approvedRecords[_albumName], string(abi.encodePacked(_albumName, " is not an approved album")));

userFavorites[msg.sender][_albumName] = true;

}

function getUserFavorites(address _user) public view returns (string[] memory) {

string[] memory favorites = new string[](9);

uint index = 0;

for (uint i = 0; i < 9; i++) {

if (userFavorites[_user][getApprovedRecordAtIndex(i)]) {

favorites[index] = getApprovedRecordAtIndex(i);

index++;

}

}

string[] memory trimmedFavorites = new string[](index);

for (uint j = 0; j < index; j++) {

trimmedFavorites[j] = favorites[j];

}

return trimmedFavorites;

}

function resetUserFavorites() public {

for (uint i = 0; i < 9; i++) {

userFavorites[msg.sender][getApprovedRecordAtIndex(i)] = false;

}

}

}

6. Structs ExerciseContract:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract GarageManager {

string private salt = "ваша строка тут";

struct Car {

string make;

string model;

string color;

uint numberOfDoors;

}

mapping(address => Car[]) public garage;

event CarAdded(address indexed owner, string make, string model, string color, uint numberOfDoors);

event CarUpdated(address indexed owner, uint indexed index, string make, string model, string color, uint numberOfDoors);

event GarageReset(address indexed owner);

function addCar(string memory _make, string memory _model, string memory _color, uint _numberOfDoors) public {

Car memory newCar = Car(_make, _model, _color, _numberOfDoors);

garage[msg.sender].push(newCar);

emit CarAdded(msg.sender, _make, _model, _color, _numberOfDoors);

}

function getMyCars() public view returns (Car[] memory) {

return garage[msg.sender];

}

function getUserCars(address _user) public view returns (Car[] memory) {

return garage[_user];

}

function updateCar(uint _index, string memory _make, string memory _model, string memory _color, uint _numberOfDoors) public {

require(_index < garage[msg.sender].length, "BadCarIndex");

garage[msg.sender][_index] = Car(_make, _model, _color, _numberOfDoors);

emit CarUpdated(msg.sender, _index, _make, _model, _color, _numberOfDoors);

}

function resetMyGarage() public {

delete garage[msg.sender];

emit GarageReset(msg.sender);

}

}

Задание сложнее

Сначала создаем контракт но не деплоим.

Contract 1:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

abstract contract Employee {

string private salt = "ваша строка тут";

uint public idNumber;

uint public managerId;

constructor(uint _idNumber, uint _managerId) {

idNumber = _idNumber;

managerId = _managerId;

}

function getAnnualCost() public view virtual returns (uint);

}

contract Salaried is Employee {

uint public annualSalary;

constructor(uint _idNumber, uint _managerId, uint _annualSalary) Employee(_idNumber, _managerId) {

annualSalary = _annualSalary;

}

function getAnnualCost() public view override returns (uint) {

return annualSalary;

}

}

contract Hourly is Employee {

uint public hourlyRate;

constructor(uint _idNumber, uint _managerId, uint _hourlyRate) Employee(_idNumber, _managerId) {

hourlyRate = _hourlyRate;

}

function getAnnualCost() public view override returns (uint) {

return hourlyRate * 2080;

}

}

contract Manager {

uint[] public employeeIds;

function addReport(uint _idNumber) public {

employeeIds.push(_idNumber);

}

function resetReports() public {

delete employeeIds;

}

}

contract Salesperson is Hourly {

constructor(uint _idNumber, uint _managerId, uint _hourlyRate) Hourly(_idNumber, _managerId, _hourlyRate) {}

}

contract EngineeringManager is Salaried, Manager {

constructor(uint _idNumber, uint _managerId, uint _annualSalary) Salaried(_idNumber, _managerId, _annualSalary) {}

}

Заходим на вкладку деплоя, выбираем в выпадающем списке Salesperson и заполняем 3 поля, как на фото:

• Id number 55555

• Manager Id number 12345

• Hourly rate 20

Жмем Transact, подписываем транзу

Копируем адрес полученного контракта себе в блокнот

Теперь выбираем EngineeringManager и заполняем 3 поля, как на фото:

• Id number 54321

• Manager Id number 11111

• Annual Salary 200000

Жмем Transact, подписываем транзу

Копируем адрес контракта в блокнот

Конец близко, но нужен еще 1 контракт, последний в этом таске квесте 😁

Создаем контракт (не забываем вставлять свою строку)

Контракт:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract InheritanceSubmission {

string private salt = "ваша строка тут";

address public salesPerson;

address public engineeringManager;

constructor(address _salesPerson, address _engineeringManager) {

salesPerson = _salesPerson;

engineeringManager = _engineeringManager;

}

}

Заходим на вкладку деплоя, и там вписываем сохраненные адреса контрактов с предыдущих шагов, соответствующие названиям.

Жмем Transact, подписываем транзу

Готово, можно субмитить контракт. Копируем адрес этого последнего контракта и идем, как обычно по ссылке

8. Imports Exercise

  1. Тут надо создать 2 контрака, а задеплоить 1, ничего сложного.

Создаем контракт именно с таким именем 👇

SillyStringUtils.sol

Вставляем этот код 👇 и ничего в нем не меняем

Contract 1:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

library SillyStringUtils {

struct Haiku {

string line1;

string line2;

string line3;

}

function shruggie(string memory _input) internal pure returns (string memory) {

return string.concat(_input, unicode" 🤷");

}

}

Затем создаем второй с любым именем в этой же папке и вставляем этот код 👇 и меняем нашу уникальную сроку

Contract 2

// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

import "./SillyStringUtils.sol";

contract ImportsExercise {

**string private salt = "ваша строка тут";**

SillyStringUtils.Haiku public haiku;

function saveHaiku(string calldata line1, string calldata line2, string calldata line3) public payable {

haiku = SillyStringUtils.Haiku({

line1: line1,

line2: line2,

line3: line3

});

}

function getHaiku() public view returns(SillyStringUtils.Haiku memory){

return haiku;

}

function shruggieHaiku() public view returns(SillyStringUtils.Haiku memory){

SillyStringUtils.Haiku memory _haiku = SillyStringUtils.Haiku({

line1: haiku.line1,

line2: haiku.line2,

line3: SillyStringUtils.shruggie(haiku.line3)

});

return _haiku;

}

}

Дальше как обычно деплоим и вставляем адрес контракта на сайте для теста

9. Error Triage Exercise

Contract

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

contract ErrorTriageExercise {

string private salt = "ваша строка тут";

// Correctly calculates the absolute difference between neighboring arguments

function diffWithNeighbor(

uint _a,

uint _b,

uint _c,

uint _d

) public pure returns (uint[] memory) {

uint[] memory results = new uint[](3);

results[0] = _a > _b ? _a - _b : _b - _a;

results[1] = _b > _c ? _b - _c : _c - _b;

results[2] = _c > _d ? _c - _d : _d - _c;

return results;

}

// Safely applies a modifier to the base value

function applyModifier(

uint _base,

int _modifier

) public pure returns (uint) {

if (_modifier < 0) {

require(_base >= uint(-_modifier), "Result would underflow");

return _base - uint(-_modifier);

} else {

return _base + uint(_modifier);

}

}

// Correctly pops the last element from the array and returns it

uint[] arr;

function popWithReturn() public returns (uint) {

require(arr.length > 0, "Array is empty");

uint index = arr.length - 1;

uint value = arr[index];

arr.pop(); // This correctly reduces the array length by 1

return value;

}

// The utility functions below are working as expected

function addToArr(uint _num) public {

arr.push(_num);

}

function getArr() public view returns (uint[] memory) {

return arr;

}

function resetArr() public {

delete arr;

}

}

AddressBook.sol

  1. Вставляем код и меняем нашу уникальную сроку

Contract 1

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";

contract AddressBook is Ownable(msg.sender) {

string private salt = "ваша строка тут";

struct Contact {

uint id;

string firstName;

string lastName;

uint[] phoneNumbers;

}

Contact[] private contacts;

mapping(uint => uint) private idToIndex;

uint private nextId = 1;

error ContactNotFound(uint id);

function addContact(string calldata firstName, string calldata lastName, uint[] calldata phoneNumbers) external onlyOwner {

contacts.push(Contact(nextId, firstName, lastName, phoneNumbers));

idToIndex[nextId] = contacts.length - 1;

nextId++;

}

function deleteContact(uint id) external onlyOwner {

uint index = idToIndex[id];

if (index >= contacts.length || contacts[index].id != id) revert ContactNotFound(id);

contacts[index] = contacts[contacts.length - 1];

idToIndex[contacts[index].id] = index;

contacts.pop();

delete idToIndex[id];

}

function getContact(uint id) external view returns (Contact memory) {

uint index = idToIndex[id];

if (index >= contacts.length || contacts[index].id != id) revert ContactNotFound(id);

return contacts[index];

}

function getAllContacts() external view returns (Contact[] memory) {

return contacts;

}

}

Сразу создаем второй контракт, вставляем код и меняем нашу уникальную сроку

Contract 2

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./AddressBook.sol";

contract AddressBookFactory {

string private salt = "ваша строка тут";

function deploy() external returns (AddressBook) {

AddressBook newAddressBook = new AddressBook();

newAddressBook.transferOwnership(msg.sender);

return newAddressBook;

}

}

Деплоим именно второй контракт, первый деплоить не нужно. Все, можем проходить тест и забирать NFT

11. Minimal Tokens Exercise

Contract

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract UnburnableToken {

string private salt = "ваша строка тут";

mapping(address => uint256) public balances;

uint256 public totalSupply;

uint256 public totalClaimed;

mapping(address => bool) private claimed;

// Custom errors

error TokensClaimed();

error AllTokensClaimed();

error UnsafeTransfer(address _to);

constructor() {

totalSupply = 100000000; // Set the total supply of tokens

}

// Public function to claim tokens

function claim() public {

if (totalClaimed >= totalSupply) revert AllTokensClaimed(); // Check if all tokens have been claimed

if (claimed[msg.sender]) revert TokensClaimed(); // Check if the caller has already claimed tokens

// Update balances and claimed status

balances[msg.sender] += 1000;

totalClaimed += 1000;

claimed[msg.sender] = true;

}

// Public function for safe token transfer

function safeTransfer(address _to, uint256 _amount) public {

// Check for unsafe transfer conditions, including if the target address has a non-zero ether balance

if (_to == address(0) || _to.balance == 0) revert UnsafeTransfer(_to);

// Ensure the sender has enough balance to transfer

require(balances[msg.sender] >= _amount, "Insufficient balance");

// Perform the transfer

balances[msg.sender] -= _amount;

balances[_to] += _amount;

}

}

Contract

Solidity

Copy

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.17;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

contract WeightedVoting is ERC20 {

string private salt = "ваша строка тут";

using EnumerableSet for EnumerableSet.AddressSet;

error TokensClaimed();

error AllTokensClaimed();

error NoTokensHeld();

error QuorumTooHigh();

error AlreadyVoted();

error VotingClosed();

struct Issue {

EnumerableSet.AddressSet voters;

string issueDesc;

uint256 quorum;

uint256 totalVotes;

uint256 votesFor;

uint256 votesAgainst;

uint256 votesAbstain;

bool passed;

bool closed;

}

struct SerializedIssue {

address[] voters;

string issueDesc;

uint256 quorum;

uint256 totalVotes;

uint256 votesFor;

uint256 votesAgainst;

uint256 votesAbstain;

bool passed;

bool closed;

}

enum Vote {

AGAINST,

FOR,

ABSTAIN

}

Issue[] internal issues;

mapping(address => bool) public tokensClaimed;

uint256 public maxSupply = 1000000;

uint256 public claimAmount = 100;

string salt = "Random String";

constructor(string memory _name, string memory _symbol)

ERC20(_name, _symbol)

{

issues.push();

}

function claim() public {

if (totalSupply() + claimAmount > maxSupply) {

revert AllTokensClaimed();

}

if (tokensClaimed[msg.sender]) {

revert TokensClaimed();

}

_mint(msg.sender, claimAmount);

tokensClaimed[msg.sender] = true;

}

function createIssue(string calldata _issueDesc, uint256 _quorum)

external

returns (uint256)

{

if (balanceOf(msg.sender) == 0) {

revert NoTokensHeld();

}

if (_quorum > totalSupply()) {

revert QuorumTooHigh();

}

Issue storage _issue = issues.push();

_issue.issueDesc = _issueDesc;

_issue.quorum = _quorum;

return issues.length - 1;

}

function getIssue(uint256 _issueId)

external

view

returns (SerializedIssue memory)

{

Issue storage _issue = issues[_issueId];

return

SerializedIssue({

voters: _issue.voters.values(),

issueDesc: _issue.issueDesc,

quorum: _issue.quorum,

totalVotes: _issue.totalVotes,

votesFor: _issue.votesFor,

votesAgainst: _issue.votesAgainst,

votesAbstain: _issue.votesAbstain,

passed: _issue.passed,

closed: _issue.closed

});

}

function vote(uint256 _issueId, Vote _vote) public {

Issue storage _issue = issues[_issueId];

if (_issue.closed) {

revert VotingClosed();

}

if (_issue.voters.contains(msg.sender)) {

revert AlreadyVoted();

}

uint256 nTokens = balanceOf(msg.sender);

if (nTokens == 0) {

revert NoTokensHeld();

}

if (_vote == Vote.AGAINST) {

_issue.votesAgainst += nTokens;

} else if (_vote == Vote.FOR) {

_issue.votesFor += nTokens;

} else {

_issue.votesAbstain += nTokens;

}

_issue.voters.add(msg.sender);

_issue.totalVotes += nTokens;

if (_issue.totalVotes >= _issue.quorum) {

_issue.closed = true;

if (_issue.votesFor > _issue.votesAgainst) {

_issue.passed = true;

}

}

}

}

Чтобы задеплоить этот контракт на странице Deploy (1) жмем галочку (2) и в окне (3) и (4) пишем любые символы, затем жмем transact (5). Готово!

13. ERC-721 Tokens Exercise

контракт:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "<https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol>";

interface ISubmission {

struct Haiku {

address author;

string line1;

string line2;

string line3;

}

function mintHaiku(

string memory _line1,

string memory _line2,

string memory _line3

) external;

// function ownerOf(uint256 _id) external view returns (address);

function counter() external view returns (uint256);

function shareHaiku(uint256 _id, address _to) external;

function getMySharedHaikus() external view returns (Haiku[] memory);

}

contract HaikuNFT is ERC721, ISubmission {

Haiku[] public haikus;

mapping(address => mapping(uint256 => bool)) public sharedHaikus;

uint256 public haikuCounter;

constructor() ERC721("HaikuNFT", "HAIKU") {

haikuCounter = 1;

}

string salt = "**ваша строка тут**";

function counter() external view override returns (uint256) {

return haikuCounter;

}

function mintHaiku(

string memory _line1,

string memory _line2,

string memory _line3

) external override {

// Check if the haiku is unique

string[3] memory haikusStrings = [_line1, _line2, _line3];

for (uint256 li = 0; li < haikusStrings.length; li++) {

string memory newLine = haikusStrings[li];

// string memory newHaikuString = string(

//     abi.encodePacked(haikusStrings[li])

// );

for (uint256 i = 0; i < haikus.length; i++) {

Haiku memory existingHaiku = haikus[i];

string[3] memory existingHaikuStrings = [

existingHaiku.line1,

existingHaiku.line2,

existingHaiku.line3

];

for (uint256 eHsi = 0; eHsi < 3; eHsi++) {

string memory existingHaikuString = existingHaikuStrings[

eHsi

];

if (

keccak256(abi.encodePacked(existingHaikuString)) ==

keccak256(abi.encodePacked(newLine))

) {

revert HaikuNotUnique();

}

}

}

}

// Mint the haiku NFT

_safeMint(msg.sender, haikuCounter);

haikus.push(Haiku(msg.sender, _line1, _line2, _line3));

haikuCounter++;

}

function shareHaiku(uint256 _id, address _to) external override {

require(_id > 0 && _id <= haikuCounter, "Invalid haiku ID");

Haiku memory haikuToShare = haikus[_id - 1];

require(haikuToShare.author == msg.sender, "NotYourHaiku");

sharedHaikus[_to][_id] = true;

}

function getMySharedHaikus()

external

view

override

returns (Haiku[] memory)

{

uint256 sharedHaikuCount;

for (uint256 i = 0; i < haikus.length; i++) {

if (sharedHaikus[msg.sender][i + 1]) {

sharedHaikuCount++;

}

}

Haiku[] memory result = new Haiku[](sharedHaikuCount);

uint256 currentIndex;

for (uint256 i = 0; i < haikus.length; i++) {

if (sharedHaikus[msg.sender][i + 1]) {

result[currentIndex] = haikus[i];

currentIndex++;

}

}

if (sharedHaikuCount == 0) {

revert NoHaikusShared();

}

return result;

}

error HaikuNotUnique();

error NotYourHaiku();

error NoHaikusShared();

}

Поздравляю, вы получили 13 NFT + 5 ролей в Дискорде, и стали чуть-чуть программистом на Solidity 🤓