Полный гайд по Base Learn + Based Developers
- Предисловие
- Подготовка
- Base Learn Newcomer
- Base Learn Acolyte
- Base Learn Consul
- Base Learn Prefect
- Base Learn Supreme
Based Developers- Заключение
Предисловие
Из всех каналов кричат о скором дропе Base токена. Говорят о необходимости делать Guild. Но ни одного гайда по Base Learn в СНГ я не нашел. Возможно они есть, но не у меня в подписках. Поэтому я решил написать его сам. Большинство информации взял у этого китайского чувака. Ворк достаточно душный вначале, но дальше все просто. В конце гайд на Based Developers. Там чисто по шаблону.
Подписывайтесь на мой тг канал, чтобы не пропускать новые гайды и статьи.
UPD: сайт guild сильно обновился, теперь нужно делать немного по-другому. Гайд полностью обновлен, за исключением Based Developers, поскольку эти пины в целом убрали. Оставил в гайде на случай повторного добавления.
Подготовка
1. Для начала нам понадобятся тестовые токены BASE. Взять их можно бесплатно на одном из трех кранов:
QuickNode Нужно иметь минимум 0.001 ETH в дефолтной сети эфира
Alchemy Нужно иметь минимум 0.001 ETH в BASE сети
Chain.link Нужно иметь минимум 1 LINK в дефолтной сети эфира
Советую QuickNode. Аlchemy у меня постоянно выдает ошибки, а покупать LINK для крана того не стоит.
Тут я думаю и без скринов все понятно, просто вводим или коннектим кошелек и жмем получить.
2. После этого заходим сюда и добавляем тестовую сеть себе в кошелек.
3. Далее переходим на guild и видим следующее. И сейчас мы будем шаг за шагом выполнять все эти квесты
Base Learn Newcomer
Начинаем с первого бейджа. Для начала мы переходим на сайт remix.etherium.
Первым делом нужно поменяем сеть в кошельке. Покажу на примере Rabby Wallet, но во всех кошельках суть одна.
- Открываем ваш кошелек
- Жмем на сеть, чтобы поменять
- Выбираем вкладку Custom Network
- Выбираем Base Sepolia
Теперь закрываем и продолжаем работу на сайте REMIX. Тут нам необходимо создать файл с кодом в левой части экрана
- Жмем на кнопку и создаем файл с пустым именем
- Вводим любое название, например "test"
- Нажимаем Enter на клавиатуре и получаем файл test.sol
Теперь необходимо скопировать следующий код:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;
interface IBasicContractTest {
function adder(
uint _a,
uint _b
) external returns (uint result, bool success);
function subtractor(
uint _a,
uint _b
) external returns (uint result, bool success);
}
contract SafeMathContract is IBasicContractTest {
function adder(
uint _a,
uint _b
) external pure override returns (uint result, bool success) {
if (type(uint).max - _a < _b) {
return (0, true);
} else {
result = _a + _b;
return (result, false);
}
}
function subtractor(
uint _a,
uint _b
) external pure override returns (uint result, bool success) {
if (_b > _a) {
return (0, true);
} else {
result = _a - _b;
return (result, false);
}
}
}Возвращаемся на сайт и компилируем код:
- Нажимаем на файл test.sol
- Нажимаем на первую строчку и вставляем скопированный код
- Нажимаем на кнопку "Solidity Compiler"
- Нажимаем на "Compiler" (0.8.30)
- Ставим ту же версию, как и вначале кода (Необязательно, если не выдает ошибок)
- Жмем "Compile"
Теперь необходимо выбрать наш кошелек как деплоер:
- Переходим во вкладку Deploy
- Нажимаем на Environment (Remix VM)
- Наводим курсор на Browser extension
- Выбираем наш кошелек
Теперь необходимо задеплоить контракт:
- Нажимаем на контракт (если его тут нет, значит вы не нажали Compile в позапрошлом шаге)
- Из контрактов выбираем SafeMathContract
- Жмем Deploy
- Проверяем, чтобы сеть была Base Sepolia
- Подписываем транзакцию
Возвращаемся на Remix и копируем адрес контракта:
- Разворачиваем контракт. (Вкладка под основным кодом)
- Находим строчку "contract address" и жмем кнопку "Скопировать"
Дальше финальная куча шагов, но ничего сложного:
- Переходим на сайт для теста контракта. Для каждого следующего бэйджа я буду давать ссылку.
- На открывшейся страницу нажимаем "contract"
- Далее жмем "write contract"
- Подключаем кошелек на кнопку "Connect to Web3"
- В появившемся окне выбираем metamask (перед этим окном может вылезти предупреждение, просто жмем ОК)
- Если у вас Rabby Wallet, то откроется такое окно. Выбираем Rabby
- Далее вылезет окно с подключением. Проверьте сверху, что сеть стоит "Base Sepolia" и жмите "connect"
- Далее жмем на "testContract"
- В появившуюся строчку вставляем адрес контракта, который мы скопировали ранее
- Нажимаем "Write"
- В появившейся транзакции проверяем сеть, чтобы была "Base Sepolia"
- Подписываем транзакцию
- После чего появится кнопка "View your transaction". Если вы сделали что-то не так, то она поменяется на ошибку через пару секунд. В таком случае попробуйте еще раз нажать "Write" и подпишите ее снова. Если после этого ошибка осталась, то советую вернуться на сайт Remix и сделать всё еще раз.
- Теперь возвращаемся на Guild и одной из этих кнопок проверяем выполнение условий.
- Поздравляю. Если вы видите зеленый значок, значит вы все сделали правильно.
Поздравляю! Мы получили первый бейджик. Остальные делаются точно так же. Просто для каждого нужно вставлять разный код. Советую брать те же названия для файлов, которые я буду указывать ниже. Перед каждым будет ссылка на сайт теста контракта. Для некоторых придется создать сразу два файла с определенными названиями. Ниже показываю код для оставшихся бейджей.
Base Learn Acolyte
Здесь нужно сделать 3 деплоя: Storage, Control Structures, Arrays. Далее код для каждого деплоя:
1. Storage Pin
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;
contract EmployeeStorage {
string public name;
uint public idNumber;
uint24 private salary;
uint16 private shares;
constructor() {
name = "Pat";
idNumber = 112358132134;
salary = 50000;
shares = 1000;
}
function grantShares(uint16 _newShares) external {
require(_newShares <= 5000, "Too many shares");
shares += _newShares;
}
function checkForPacking(uint _slot) external view returns (uint result) {
assembly {
result := sload(_slot)
}
}
function viewShares() external view returns (uint16) {
return shares;
}
function viewSalary() external view returns (uint24) {
return salary;
}
function debugResetShares() external {
shares = 1000;
}
}
2. Control Structures Pin
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;
contract ControlStructures {
error AfterHours(uint256 time);
function fizzBuzz(uint256 _number) external pure returns (string memory) {
if (_number % 15 == 0) {
return "FizzBuzz";
} else if (_number % 3 == 0) {
return "Fizz";
} else if (_number % 5 == 0) {
return "Buzz";
} else {
return "Splat";
}
}
function doNotDisturb(uint256 _time) external pure returns (string memory) {
assert(_time <= 2400);
if (_time <= 800 || _time > 2200) {
revert AfterHours(_time);
}
if (_time > 800 && _time <= 1200) {
return "Morning!";
} else if (_time > 1200 && _time <= 1800) {
return "Afternoon!";
} else {
return "Evening!";
}
}
}
3. Arrays Pin
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;
contract Submission {
uint[] private numbers;
uint[] private timestamps;
address[] private senders;
constructor() {
resetNumbers();
}
function resetNumbers() public {
delete numbers;
for (uint i = 1; i <= 10; i++) {
numbers.push(i);
}
}
function appendToNumbers(uint[] calldata _toAppend) external {
for (uint i = 0; i < _toAppend.length; i++) {
numbers.push(_toAppend[i]);
}
}
function getNumbers() external view returns (uint[] memory) {
return numbers;
}
function saveTimestamp(uint _unixTimestamp) external {
timestamps.push(_unixTimestamp);
senders.push(msg.sender);
}
function resetTimestamps() external {
delete timestamps;
}
function resetSenders() external {
delete senders;
}
function afterY2K() external view returns (uint[] memory, address[] memory) {
uint count = 0;
for (uint i = 0; i < timestamps.length; i++) {
if (timestamps[i] >= 946702900) {
count++;
}
}
uint[] memory filteredTimestamps = new uint[](count);
address[] memory filteredAddresses = new address[](count);
uint counter = 0;
for (uint i = 0; i < timestamps.length; i++) {
if (timestamps[i] >= 946702900) {
filteredTimestamps[counter] = timestamps[i];
filteredAddresses[counter] = senders[i];
counter++;
}
}
return (filteredTimestamps, filteredAddresses);
}
}
Base Learn Consul
Здесь нужно сделать 3 деплоя: Mappings, Inheritance, Structs. Далее код для каждого деплоя:
1. Mappings
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;
interface ISubmission {
function getApprovedRecords() external view returns (string[] memory);
function addRecord(string memory _albumName) external;
function getUserFavorites(
address _address
) external view returns (string[] memory);
function resetUserFavorites() external;
}
contract Submission is ISubmission {
mapping(address => string[]) public userFavorites;
function getApprovedRecords()
external
pure
override
returns (string[] memory)
{
string[] memory approved = new string[](9);
approved[0] = "Thriller";
approved[1] = "Back in Black";
approved[2] = "The Bodyguard";
approved[3] = "The Dark Side of the Moon";
approved[4] = "Their Greatest Hits (1971-1975)";
approved[5] = "Hotel California";
approved[6] = "Come On Over";
approved[7] = "Rumours";
approved[8] = "Saturday Night Fever";
return approved;
}
function addRecord(string memory _albumName) external override {
userFavorites[msg.sender].push(_albumName);
}
function getUserFavorites(
address _address
) external view override returns (string[] memory) {
return userFavorites[_address];
}
function resetUserFavorites() external override {
delete userFavorites[msg.sender];
}
}2. Inheritance
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;
interface IEmployee {
function idNumber() external returns (uint);
function managerId() external returns (uint);
}
interface ISalaried is IEmployee {
function annualSalary() external returns (uint);
}
interface IHourly is IEmployee {
function hourlyRate() external returns (uint);
}
interface ISalesPerson is IHourly {}
interface IEngineeringManager is ISalaried {}
interface IInheritanceSubmission {
function salesPerson() external returns (address);
function engineeringManager() external returns (address);
}
contract SalesPerson is ISalesPerson {
function hourlyRate() external pure override returns (uint) {
return 20;
}
function idNumber() external pure override returns (uint) {
return 55555;
}
function managerId() external pure override returns (uint) {
return 0;
}
}
contract EngineeringManager is IEngineeringManager {
function annualSalary() external pure override returns (uint) {
return 200_000;
}
function managerId() external pure override returns (uint) {
return 11111;
}
function idNumber() external pure override returns (uint) {
return 0;
}
}
contract InheritanceSubmission is IInheritanceSubmission {
SalesPerson private _salesPerson;
EngineeringManager private _engineeringManager;
constructor() {
_salesPerson = new SalesPerson();
_engineeringManager = new EngineeringManager();
}
function salesPerson() external view override returns (address) {
return address(_salesPerson);
}
function engineeringManager() external view override returns (address) {
return address(_engineeringManager);
}
}Выбираем InheritanceSubmission
3. Structs
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;
interface ISubmission {
struct Car {
string make;
string model;
string color;
uint numberOfDoors;
}
function addCar(
string calldata _make,
string calldata _model,
string calldata _color,
uint _numberOfDoors
) external;
function updateCar(
uint _index,
string calldata _make,
string calldata _model,
string calldata _color,
uint _numberOfDoors
) external;
function getUserCars(address _user) external view returns (Car[] memory);
function getMyCars() external view returns (Car[] memory);
function resetMyGarage() external;
}
contract Submission is ISubmission {
mapping(address => Car[]) private garages;
function addCar(
string calldata _make,
string calldata _model,
string calldata _color,
uint _numberOfDoors
) external override {
Car memory newCar = Car({
make: _make,
model: _model,
color: _color,
numberOfDoors: _numberOfDoors
});
garages[msg.sender].push(newCar);
}
function updateCar(
uint _index,
string calldata _make,
string calldata _model,
string calldata _color,
uint _numberOfDoors
) external override {
Car storage carToUpdate = garages[msg.sender][_index];
carToUpdate.make = _make;
carToUpdate.model = _model;
carToUpdate.color = _color;
carToUpdate.numberOfDoors = _numberOfDoors;
}
function getUserCars(
address _user
) external view override returns (Car[] memory) {
return garages[_user];
}
function getMyCars() external view override returns (Car[] memory) {
return garages[msg.sender];
}
function resetMyGarage() external override {
delete garages[msg.sender];
}
}Base Learn Prefect
Здесь нужно сделать 3 деплоя: Error Triage, New Keyword, Imports. Далее код для каждого деплоя:
1. Error Triage
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract ErrorTriageExercise {
/**
* @dev Finds the difference between each uint with its neighbor (a to b, b to c, etc.)
* and returns a uint array with the absolute integer difference of each pairing.
*
* @param _a The first unsigned integer.
* @param _b The second unsigned integer.
* @param _c The third unsigned integer.
* @param _d The fourth unsigned integer.
*
* @return results An array containing the absolute differences between each pair of integers.
*/
function diffWithNeighbor(
uint _a,
uint _b,
uint _c,
uint _d
) public pure returns (uint[] memory) {
// Initialize an array to store the differences
uint[] memory results = new uint[](3);
// Calculate the absolute difference between each pair of integers and store it in the results array
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 the array of differences
return results;
}
/**
* @dev Changes the base by the value of the modifier. Base is always >= 1000. Modifiers can be
* between positive and negative 100.
*
* @param _base The base value to be modified.
* @param _modifier The value by which the base should be modified.
*
* @return returnValue The modified value of the base.
*/
function applyModifier(
uint _base,
int _modifier
) public pure returns (uint returnValue) {
// Apply the modifier to the base value
if(_modifier > 0) {
return _base + uint(_modifier);
}
return _base - uint(-_modifier);
}
uint[] arr;
function popWithReturn() public returns (uint returnNum) {
if(arr.length > 0) {
uint result = arr[arr.length - 1];
arr.pop();
return result;
}
}
// 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;
}
}
2. New Keyword
Нажать "Compile" нужно на двух контрактах перед деплоем!
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;
import "@openzeppelin/contracts/access/Ownable.sol";
contract AddressBook is Ownable {
// Define a private salt value for internal use
string private salt = "value";
// Define a struct to represent a contact
struct Contact {
uint id; // Unique identifier for the contact
string firstName; // First name of the contact
string lastName; // Last name of the contact
uint[] phoneNumbers; // Array to store multiple phone numbers for the contact
}
// Array to store all contacts
Contact[] private contacts;
// Mapping to store the index of each contact in the contacts array using its ID
mapping(uint => uint) private idToIndex;
// Variable to keep track of the ID for the next contact
uint private nextId = 1;
// Custom error for when a contact is not found
error ContactNotFound(uint id);
// 🔧 FIX: Constructor eklendi - Ownable için gerekli
constructor() Ownable(msg.sender) {}
// Function to add a new contact
function addContact(string calldata firstName, string calldata lastName, uint[] calldata phoneNumbers) external onlyOwner {
// Create a new contact with the provided details and add it to the contacts array
contacts.push(Contact(nextId, firstName, lastName, phoneNumbers));
// Map the ID of the new contact to its index in the array
idToIndex[nextId] = contacts.length - 1;
// Increment the nextId for the next contact
nextId++;
}
// Function to delete a contact by its ID
function deleteContact(uint id) external onlyOwner {
// Get the index of the contact to be deleted
uint index = idToIndex[id];
// Check if the index is valid and if the contact with the provided ID exists
if (index >= contacts.length || contacts[index].id != id) revert ContactNotFound(id);
// Replace the contact to be deleted with the last contact in the array
contacts[index] = contacts[contacts.length - 1];
// Update the index mapping for the moved contact
idToIndex[contacts[index].id] = index;
// Remove the last contact from the array
contacts.pop();
// Delete the mapping entry for the deleted contact ID
delete idToIndex[id];
}
// Function to retrieve a contact by its ID
function getContact(uint id) external view returns (Contact memory) {
// Get the index of the contact
uint index = idToIndex[id];
// Check if the index is valid and if the contact with the provided ID exists
if (index >= contacts.length || contacts[index].id != id) revert ContactNotFound(id);
// Return the contact details
return contacts[index];
}
// Function to retrieve all contacts
function getAllContacts() external view returns (Contact[] memory) {
// Return the array of all contacts
return contacts;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
// Import the AddressBook contract to interact with it
import "./AddressBook.sol";
// Contract for creating new instances of AddressBook
contract AddressBookFactory {
// Define a private salt value for internal use
string private salt = "value";
// Function to deploy a new instance of AddressBook
function deploy() external returns (AddressBook) {
// Create a new instance of AddressBook
AddressBook newAddressBook = new AddressBook();
// Transfer ownership of the new AddressBook contract to the caller of this function
newAddressBook.transferOwnership(msg.sender);
// Return the newly created AddressBook contract
return newAddressBook;
}
}Выбираем AddressBookFactory - other contract
3. Imports
Нажать "Compile" нужно на двух контрактах перед деплоем!
// 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" 🤷");
}
}// SPDX-License-Identifier: MIT
// Importing the SillyStringUtils library
import "./SillyStringUtils.sol";
pragma solidity 0.8.17;
contract ImportsExercise {
// Using the SillyStringUtils library for string manipulation
using SillyStringUtils for string;
// Declaring a public variable to store a Haiku
SillyStringUtils.Haiku public haiku;
// Function to save a Haiku
function saveHaiku(string memory _line1, string memory _line2, string memory _line3) public {
haiku.line1 = _line1;
haiku.line2 = _line2;
haiku.line3 = _line3;
}
// Function to retrieve the saved Haiku
function getHaiku() public view returns (SillyStringUtils.Haiku memory) {
return haiku;
}
// Function to append a shrugging emoji to the third line of the Haiku
function shruggieHaiku() public view returns (SillyStringUtils.Haiku memory) {
// Creating a copy of the Haiku
SillyStringUtils.Haiku memory newHaiku = haiku;
// Appending the shrugging emoji to the third line using the shruggie function from the SillyStringUtils library
newHaiku.line3 = newHaiku.line3.shruggie();
return newHaiku;
}
}
Выбираем ImportsExercise - imports.sol
Base Learn Supreme
Здесь нужно сделать 3 деплоя: SCD ERC721, Minimal Token, ERC20. Далее код для каждого деплоя:
1. SCD ERC721
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
// Importing OpenZeppelin ERC721 contract (совместимая версия без mcopy)
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.9.3/contracts/token/ERC721/ERC721.sol";
// Interface for interacting with a submission contract
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 counter() external view returns (uint256);
function shareHaiku(uint256 _id, address _to) external;
function getMySharedHaikus() external view returns (Haiku[] memory);
}
// Contract for managing Haiku NFTs
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 = "value";
function counter() external view override returns (uint256) {
return haikuCounter;
}
function mintHaiku(
string memory _line1,
string memory _line2,
string memory _line3
) external override {
string[3] memory haikusStrings = [_line1, _line2, _line3];
for (uint256 li = 0; li < haikusStrings.length; li++) {
string memory newLine = 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();
}
}
}
}
_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();
}Выбираем HaikuNFT
2. Minimal Token
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
// Contract for an unburnable token
contract UnburnableToken {
string private salt = "value"; // A private string variable
// Mapping to track token balances of addresses
mapping(address => uint256) public balances;
uint256 public totalSupply; // Total supply of tokens
uint256 public totalClaimed; // Total number of tokens claimed
mapping(address => bool) private claimed; // Mapping to track whether an address has claimed tokens
// Custom errors
error TokensClaimed(); // Error for attempting to claim tokens again
error AllTokensClaimed(); // Error for attempting to claim tokens when all are already claimed
error UnsafeTransfer(address _to); // Error for unsafe token transfer
// Constructor to set the total supply of tokens
constructor() {
totalSupply = 100000000; // Set the total supply of tokens
}
// Public function to claim tokens
function claim() public {
// Check if all tokens have been claimed
if (totalClaimed >= totalSupply) revert AllTokensClaimed();
// Check if the caller has already claimed tokens
if (claimed[msg.sender]) revert TokensClaimed();
// 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;
}
}
3. ERC20
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;
// Importing OpenZeppelin contracts for ERC20 and EnumerableSet functionalities
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
// Contract for weighted voting using ERC20 token
contract WeightedVoting is ERC20 {
string private salt = "value"; // A private string variable
using EnumerableSet for EnumerableSet.AddressSet; // Importing EnumerableSet for address set functionality
// Custom errors
error TokensClaimed(); // Error for attempting to claim tokens again
error AllTokensClaimed(); // Error for attempting to claim tokens when all are already claimed
error NoTokensHeld(); // Error for attempting to perform an action without holding tokens
error QuorumTooHigh(); // Error for setting a quorum higher than total supply
error AlreadyVoted(); // Error for attempting to vote more than once
error VotingClosed(); // Error for attempting to vote on a closed issue
// Struct to represent an issue
struct Issue {
EnumerableSet.AddressSet voters; // Set of voters
string issueDesc; // Description of the issue
uint256 quorum; // Quorum required to close the issue
uint256 totalVotes; // Total number of votes casted
uint256 votesFor; // Total number of votes in favor
uint256 votesAgainst; // Total number of votes against
uint256 votesAbstain; // Total number of abstained votes
bool passed; // Flag indicating if the issue passed
bool closed; // Flag indicating if the issue is closed
}
// Struct to represent a serialized issue
struct SerializedIssue {
address[] voters; // Array of voters
string issueDesc; // Description of the issue
uint256 quorum; // Quorum required to close the issue
uint256 totalVotes; // Total number of votes casted
uint256 votesFor; // Total number of votes in favor
uint256 votesAgainst; // Total number of votes against
uint256 votesAbstain; // Total number of abstained votes
bool passed; // Flag indicating if the issue passed
bool closed; // Flag indicating if the issue is closed
}
// Enum to represent different vote options
enum Vote {
AGAINST,
FOR,
ABSTAIN
}
// Array to store all issues
Issue[] internal issues;
// Mapping to track if tokens are claimed by an address
mapping(address => bool) public tokensClaimed;
uint256 public maxSupply = 1000000; // Maximum supply of tokens
uint256 public claimAmount = 100; // Amount of tokens to be claimed
string saltt = "any"; // Another string variable
// Constructor to initialize ERC20 token with a name and symbol
constructor(string memory _name, string memory _symbol)
ERC20(_name, _symbol)
{
issues.push(); // Pushing an empty issue to start from index 1
}
// Function to claim tokens
function claim() public {
// Check if all tokens have been claimed
if (totalSupply() + claimAmount > maxSupply) {
revert AllTokensClaimed();
}
// Check if the caller has already claimed tokens
if (tokensClaimed[msg.sender]) {
revert TokensClaimed();
}
// Mint tokens to the caller
_mint(msg.sender, claimAmount);
tokensClaimed[msg.sender] = true; // Mark tokens as claimed
}
// Function to create a new voting issue
function createIssue(string calldata _issueDesc, uint256 _quorum)
external
returns (uint256)
{
// Check if the caller holds any tokens
if (balanceOf(msg.sender) == 0) {
revert NoTokensHeld();
}
// Check if the specified quorum is higher than total supply
if (_quorum > totalSupply()) {
revert QuorumTooHigh();
}
// Create a new issue and return its index
Issue storage _issue = issues.push();
_issue.issueDesc = _issueDesc;
_issue.quorum = _quorum;
return issues.length - 1;
}
// Function to get details of a voting issue
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 to cast a vote on a voting issue
function vote(uint256 _issueId, Vote _vote) public {
Issue storage _issue = issues[_issueId];
// Check if the issue is closed
if (_issue.closed) {
revert VotingClosed();
}
// Check if the caller has already voted
if (_issue.voters.contains(msg.sender)) {
revert AlreadyVoted();
}
uint256 nTokens = balanceOf(msg.sender);
// Check if the caller holds any tokens
if (nTokens == 0) {
revert NoTokensHeld();
}
// Update vote counts based on the vote option
if (_vote == Vote.AGAINST) {
_issue.votesAgainst += nTokens;
} else if (_vote == Vote.FOR) {
_issue.votesFor += nTokens;
} else {
_issue.votesAbstain += nTokens;
}
// Add the caller to the list of voters and update total votes count
_issue.voters.add(msg.sender);
_issue.totalVotes += nTokens;
// Close the issue if quorum is reached and determine if it passed
if (_issue.totalVotes >= _issue.quorum) {
_issue.closed = true;
if (_issue.votesFor > _issue.votesAgainst) {
_issue.passed = true;
}
}
}
}
Выбираем WeighhtedVoting. Жмем стрелочку около деплоя и там вводим название и символ нашего токена (какие захотите).
Поздравляю! Мы получили все бейджи во вкладке Base Learn. Далее разберем Based Developers. Делается очень быстро. По сути нужно 5 раз задеплоить один и тот же контракт в мейн сети BASE.
Based Developers
UPD: Этих квестов в новом гилде нет, так что вся дальнейшая информация НЕ АКТУАЛЬНА!!! Оставил ее на случай повторного добавления этих квестов.
Создаем новый файл с любым названием и вставляем туда следующий код (НЕ ДЕПЛОИМ, СНАЧАЛА ЧИТАЕМ ИНФУ ПОД КОДОМ):
// 0.5.1-c8a2
// Enable optimization
pragma solidity ^0.5.0;
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
contract ERC20 is IERC20 {
using SafeMath for uint256;
mapping (address => uint256) private _balances;
mapping (address => mapping (address => uint256)) private _allowances;
uint256 private _totalSupply;
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view returns (uint256) {
return _balances[account];
}
function transfer(address recipient, uint256 amount) public returns (bool) {
_transfer(msg.sender, recipient, amount);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view returns (uint256) {
return _allowances[owner][spender];
}
function approve(address spender, uint256 value) public returns (bool) {
_approve(msg.sender, spender, value);
return true;
}
function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
_transfer(sender, recipient, amount);
_approve(sender, msg.sender, _allowances[sender][msg.sender].sub(amount));
return true;
}
function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
_approve(msg.sender, spender, _allowances[msg.sender][spender].add(addedValue));
return true;
}
function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
_approve(msg.sender, spender, _allowances[msg.sender][spender].sub(subtractedValue));
return true;
}
function _transfer(address sender, address recipient, uint256 amount) internal {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
_balances[sender] = _balances[sender].sub(amount);
_balances[recipient] = _balances[recipient].add(amount);
emit Transfer(sender, recipient, amount);
}
function _mint(address account, uint256 amount) internal {
require(account != address(0), "ERC20: mint to the zero address");
_totalSupply = _totalSupply.add(amount);
_balances[account] = _balances[account].add(amount);
emit Transfer(address(0), account, amount);
}
function _burn(address account, uint256 value) internal {
require(account != address(0), "ERC20: burn from the zero address");
_totalSupply = _totalSupply.sub(value);
_balances[account] = _balances[account].sub(value);
emit Transfer(account, address(0), value);
}
function _approve(address owner, address spender, uint256 value) internal {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = value;
emit Approval(owner, spender, value);
}
/**
* @dev Destoys `amount` tokens from `account`.`amount` is then deducted
* from the caller's allowance.
*
* See {_burn} and {_approve}.
*/
function _burnFrom(address account, uint256 amount) internal {
_burn(account, amount);
_approve(account, msg.sender, _allowances[account][msg.sender].sub(amount));
}
}
/**
* @dev Optional functions from the ERC20 standard.
*/
contract ERC20Detailed is IERC20 {
string private _name;
string private _symbol;
uint8 private _decimals;
/**
* @dev Sets the values for `name`, `symbol`, and `decimals`. All three of
* these values are immutable: they can only be set once during
* construction.
*/
constructor (string memory name, string memory symbol, uint8 decimals) public {
_name = name;
_symbol = symbol;
_decimals = decimals;
}
/**
* @dev Returns the name of the token.
*/
function name() public view returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view returns (string memory) {
return _symbol;
}
function decimals() public view returns (uint8) {
return _decimals;
}
}
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction overflow");
uint256 c = a - b;
return c;
}
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, "SafeMath: division by zero");
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0, "SafeMath: modulo by zero");
return a % b;
}
}
contract Token is ERC20, ERC20Detailed {
/**
* @dev Constructor that gives msg.sender all of existing tokens.
*/
constructor () public ERC20Detailed("TEST", "TT", 6) {
_mint(msg.sender, 21000000* (10 ** uint256(decimals())));
}
}
Последняя строчка выглядит так:
Перед деплоем поменяйте название, символ и количество (на всякий случай). И так деплоим 5 разных токенов по очереди.
Контракт выбираем - Token. И не забудьте поменять версию компилятора как вначале кода - 0.5.0.
На этом все. Этого достаточно чтобы получить оба бейджа.
Заключение
Если вы дошли до сюда, значит вы полностью выполнили Base Learn и Based Developers. Всего их прошло ~40k кошельков, что мало для такой сети. После переезда на новый гилд будет еще меньше, поскольку не все заклеймят всё снова. Спасибо за прочтение статьи.
Подписывайтесь, чтобы не пропускать другие статьи по отработке проектов - тявель.