bomb diggity tired – Telegram
bomb diggity tired
2.07K subscribers
55 photos
1 video
10 files
138 links
research web3
Download Telegram
bomb diggity tired pinned «In Progress [1] С начала [2] Инструменты [3] Статьи / книги [4] Смарт-контракты»
Прошел месяц как начал погружение в Solidity.

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

Также выполнил 11 уровней CryptoZombies, прошелся по функционалу в Remixe и Visual Studio Code, установил Hardhat, Node.js

Некоторые выводы и план действий на следующие три месяца:

1) Сфокусироваться только на Solidity. (не отвлекаться на другие языки)
2) 80% на практику / 20% теории.
2.1) практика нацеленная на разбор кода и поиск ошибок.
3) Найти для себя pet-проект.
5
Boom! Начинаю серию написания и разборов смарт-контрактов от простых к сложным.

Напишем простой смарт-контракт и разберем как он работает.

[1] Создадим фаил в Remixe например: Hodler (наименование контрактов, библиотек, событий и структур принято называть с заглавной буквы)
c разрешением .sol

[2] Смарт-контракт - начинается с описания лицензии.
// (два слэша) - это однострочный комментарий. Комментарии предназначены для чтения человеком и не включаются в исполняемый байткод EVM. Лицензия обычно используется MIT (свободного доступа) например:
// SPDX-License-Identifier: MIT

[3] Указываем версию компилятора: переходим во вкладку Solidity Compiler выбираю последнию: 0.8.23 в контракте прописываю: pragma solidity ^0.8.23;

[4] Пишем сам смарт-контракт.
Создаем contract тело контракта включает в себя все строки между фигурными скобками { }
назовем например: Balance создаем функцию receive() указываем модификатор external payable {}
(объявление функции receive(), которая является внешней (external) и принимает эфир (payable). Это означает, что функция может быть вызвана извне контракта для принятия эфира)
contract Balance {
receive() external payable {}


[5] Создаем функцию снятия эфира с контракта с ограничением по максимальной сумме.
function withdraw(uint withdraw_amount) public {
require(withdraw_amount <= 200000000000000000);

Функция withdraw которая принимает один аргумент withdrawamount типа uint (т.е. целое число без знака). Ключевое слово public указывает на то, что функция является доступной для вызова извне контракта.

Далее, строка require(withdrawamount <= 200000000000000000); представляет собой проверку, которая гарантирует, что значение withdrawamount не превышает 200000000000000000 Wei (или 0,2 Eth) Eth в Wei конвертирую в Convert Ethereum. Если значение withdrawamount больше этого числа, то выполнение функции будет прервано, и эфир не будет выведен. Прописываем вывод денежных средств payable(msg.sender).transfer(withdraw_amount);
msg представляет собой объект, который содержит информацию о текущей транзакции, атрибут sender — это адрес отправителя транзакции.transfer(withdraw_amount) — это встроенная функция Solidity, которая позволяет контракту отправить эфир указанному адресу.
Таким образом, отправляем указанное количество эфира обратно тому адресу, который инициировал вызов данной функции контракта.

// SPDX-License-Identifier: MIT 
pragma solidity ^0.8.23;

contract Balance {
receive() external payable {}

function withdraw(uint withdraw_amount) public {
require(withdraw_amount <= 200000000000000000);

payable(msg.sender).transfer(withdraw_amount);
}
}

Основная логика нашего смарт-контракта - принятие эфира, управление выводом средств и установка лимита на снятие за одну транзакцию.

[6] Компиляция и Deploy смарт-контракта.
Используем компилятор Solidity для преобразования кода Solidity в байткод EVM, чтобы он мог быть выполнен на блокчейне. Переходим в Solidity Compiler - компилируем и переходим во вкладку Deploy. В Enviromente выбираем WalletConnect, подключаем свой MetaMask в сети Goerli (на счету необходимы GoerliETH) и разворачиваем контракт.

Появился адрес контракта: 0xF80941FD1Eb4aE9e303C27180Dd020095Cd9a952

https://goerli.etherscan.io/address/0xF80941FD1Eb4aE9e303C27180Dd020095Cd9a952

Теперь пополним контракт и запросим вывод равный 0,2 ETH и бо́льшую сумму (транзакция как и должна прервалась) https://goerli.etherscan.io/tx/0x96c29f430819744765cc73feb38d40c98015dd3fe857354afb37f3ff9760d5b4

В remixe на балансе также отображается эфир. Максимальная сумма вывода за одну транзакцию по контракту: 200000000000000000 Wei или 0,2 ETH

[7] Вывод: написали смарт-контракт, после деплоя появился адрес, отправили на него 0,5 ETH, создали транзакцию для вызова функции withdraw и успешно запросили 0,4 эфира (2 раза по 0,2) Контракт проверил запросы и отправил нам эфир с помощью внутренних транзакций.
👍7
«Raffle» смарт-контракт. Часть 1

Логика контракта: мемберы рафла депонируют Eth на смарт-контракт. Владелец запускает розыгрыш, дальше рандомным способом выбирается победитель и все эфиры переводятся на его кошелек.

Описание лицензии и версии пропускаю это было в предыдущем смарт-контракте.

[1] Объявляем контракт, ключевое слово: contract его название Raffle и через синтаксис фигурные скобки указываем переменные контракта. Первая переменная WillyWonka – владелец контракта. Тип данных address адрес будет публичный public, доступен извне контракта. Дальше задаем переменную members (динамический массив адресов участников, которые могут вносить свои ставки) тип данных: address public но есть модификатор payble – дуступно принятие денежных средств. Задаем переменную winner – победитель, тип данных address public с модификатором payble

contract Raffle {
address public WillyWonka;
address payable[] public members;
address payable public winner;
}

[2] Создаем конструктор – функция которая вызывается автоматически и однократно в момент развертывания смарт-контракта constructor() { в нем указываем глобальную переменную msg.sender (кто отправитель разворачиваемого контракта или другими словами адрес нашего кошелька, разворачиваемого контракта) тот и владелец WillyWonka= msg.sender; (эти данные сохраняются в блокчейне) Дальше пропишем функцию чтобы участники могли присоединиться к событию, function join() public payable { Функция join позволяет участникам присоединиться к розыгрышу, при условии что они отправляют 0,005 эфира. Функция goBalance возвращает текущий баланс контракта, доступная только для WillyWonka. Функция random возвращает случайное число на основе временной метки блока, предыдущего случайного числа и длины массива участников. Функция goWinner позволяет выбрать победителя, при условии, что она вызвана WillyWonka и количество участников больше или равно 4. Выигравший участник получает баланс контракта, затем массив участников обнуляется.

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.23;

contract Raffle {
address public WillyWonka;
address payable[] public members;
address payable public winner;

constructor(){
WillyWonka=msg.sender;
}

function join() public payable{
require(msg.value==5000000000000000 wei, "only pay 0.005 ether");
members.push(payable(msg.sender));
}

function goBalance() public view returns(uint){
require(WillyWonka==msg.sender, "Never doubt what no one knows");
return address(this).balance;
}

function random() internal view returns(uint){
return uint(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, members.length)));
}

function goWinner() public{
require(WillyWonka==msg.sender, "Never doubt what no one knows");
require(members.length>=4, "Members are less than 4");
uint r=random();
uint index = r%members.length;
winner=members[index];
winner.transfer(goBalance());
members= new address payable[](0);
}
}


[3] Компилируем и деплоим смарт-контракт, появляется адрес и интерфейс с активными кнопками (goWinner, join, goBalance, members, WillyWonka, winner) Кнопки синего цвета – это считывание информации из вне, (это не транзакция, а вызов [call] для чтения данных, и они бесплатные. Красная кнопка – принятия средств (payable) на контракт. Оранжевая - переводит деньги куда-то, но сама деньги принимать не может. Вызывается через транзакцию и за ее вызов необходимо платить. В нашем случае отправка выигрыша победителю.

В разделе Account поочередно выбираем 4 кошелька и с каждого принимаем участие в размере 0,005 Eth. C адреса – владелец контракта нажимаем goWinner -> функция рандом с помощью переменных блока определяет победителя и отправляет средства на счет.

[4] Вывод: Написали «Raffle» смарт-контракт и провели розыгрыш, но можно ли считать данный метод с переменными блока действительно случайным и устойчивым к атакам? И какие еще есть подходы в генерации случайных чисел, разберем во 2 части.
👍6
Уязвимость в контроле доступа.

Представим как хорошо было бы в наших смарт-контрактах устанавливать новых владельцев.

Скажем, в смарт-контракте «Raffle» владелец (owner) WillyWonka захотел назначить владельцем Чарли Бакета новый кошелек. И да, это сделать возможно, необходимо написать функцию которая будет называться setOwner (установить владельца)
    function setOwner(address NewOwner) public {
WillyWonka = NewOwner;
}

Теперь легко можем это осуществить и все довольны. Но скажем эту функцию случайно увидел Matapac и быстро завладел нашим смарт-контрактом 😣 (конечно сообщил об уязвимости 🥳) Разберем, почему так произошло и что с этим можно сделать.

[1] В данной реализации функции setOwner отсутствует проверка на то, что вызывающий адрес равен текущему владельцу контракта. Это может означать, что любой адрес может вызвать эту функцию и изменить владельца контракта.

[2] Для обеспечения безопасности контракта, нужно добавить проверку, которая бы гарантировала, что только текущий владелец контракта может изменить свой адрес.

[3] Это можно сделать с помощью модификаторов или с использованием функции require.
    function setOwner(address NewOwner) public {
require(msg.sender == WillyWonka, "You're not the owner");
WillyWonka = NewOwner;
}

[4] Теперь установлена проверка, что вызывающий адрес равен текущему владельцу контракта. Если это условие не выполняется, появится сообщение "You're not the owner". Если проверка проходит, то новый адрес, переданный в качестве параметра, устанавливается новым владельцем контракта.

[5] Совершили простую реализацию функции смены владельца, при этом соблюдается контроль доступа с помощью проверки вызывающего адреса.
5
Механика голландского NFT аукциона.

В самом начале объявляется максимальная стоимость предмета. После цена постепенно снижается до тех пор, пока не найдется покупатель или не закончится время.
// SPDX-License-Identifier: MIT  
pragma solidity >=0.8.23;

contract DutchAuction {
//Параметры:
uint public initialPrice; // Начальная цена (максимальная цена NFT);
uint public biddingPerid; // Переменная длительности периода торгов в секундах;
uint public offerPriceDecrement; // Снижение цены предложения (сумма на которую цена NFT уменьшается с каждой секундой);
uint public startTime; // Время начала;
NftToken public boom; // Адрес нашего NFT токена который был выставлен на продажу;
address payable public seller; // Адрес продавца, который получит все средства с продажи NFT;
address payable winnerAddress; // Изначально равно 0 и будет адресом победителя, кто первый сделает выигрышную ставку, того NFT, адрес победителя сохранит значение;

function buyNow() public payable { // Функция торгов «Купить сейчас», кто первый предоставит достаточно средств, того NFT;
uint timeElapsed = block.timestamp - startTime; // Логика покупки: мы должны выяснить какова текущая цена (currPrice), поэтому текущая цена будет зависеть от того сколько времени прошло (timeElapsed) с момента начала аукциона, прошедшее время будет текущей меткой блока (block.timestamp) минус время начала (startTime);
uint currPrice = initialPrice - (timeElapsed * offerPriceDecrement); // Текущая цена будет начальной ценой минус (количество прошедших секунд умноженное на снижения цены предложения);
uint userBid = msg. value; // Пользовательская ставка - вся сумма которую пользователь предоставляет;
// Операторы - выполняют необходимые проверки действительности ставки;
require(winnerAddress == address(0)); // Адрес победителя равен 0, что означает, что никто еще не заявил права на NFT (аукцион продолжается);
require(timeElapsed < biddingPeriod); // Ставка делается своевременно до истечения времени (время прошедшее с момента начала < периода торгов);
require(userBid >= currPrice); // Пользователь предоставил достаточно средств; (ставка больше, либо равно текущей цене);
winnerAddress = payable(msg.sender); // Если все условия выше выполнены = ставка действительна;
winnerAddress.transfer(userBid - currPrice); // Возврат разницы победителю (размер ставки минус текущая цена)
seller.transfer(currPrice); // Текущая цена будет передана продавцу;
boom.transferOwnership(winnerAddress); // Вызываем функцию передачи права собственности на NFT и передаем от текущего владельца победителю;
}
}

Таким образом, продавец может получить максимальную возможную цену за свой NFT, а покупатель - выгодную сделку при нахождении оптимальной цены.
5
Boom! Погружение в виртуальную машину Ethereum.

Документация Solidity описывает расположение переменных состояния в хранилище.

• В чем разница между string, bytes32, byte[], bytes?
• Какой из них использовать и когда?
• Что происходит, когда приводим строку к байтам?
• Можно ли привести к byte[]?
• Сколько это стоит?
• Как mappings хранится в EVM?
• Как скомпилированный контракт выглядит для EVM?

EVM — это механизм базы данных. Чтобы понять, как работают смарт-контракты на любом языке EVM, необходимо понимать, как данные организуются, хранятся и ими манипулируют.

В серии статей разобраны простые контракты Solidity, чтобы понять, как они работают в качестве байткода EVM.
• Основы байткода EVM.
• Введение в ассемблерный код EVM.
• Как представлены различные типы (mapping, arrays).
• Что происходит при создании нового контракта.
• Что происходит при вызове метода.
• Как ABI объединяет разные языки EVM.


Таблица набора инструкций EVM будет полезным справочником.

Конечная цель — полностью понять скомпилированный контракт Solidity. Начнем с чтения базового байткода EVM.

Погружение в виртуальную машину Ethereum. Часть 1

https://blog.qtum.org/diving-into-the-ethereum-vm-6e8d5d2f3c30

Погружение в виртуальную машину Ethereum. Часть 2

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

https://medium.com/@hayeah/diving-into-the-ethereum-vm-part-2-storage-layout-bc5349cb11b7

Погружение в виртуальную машину Ethereum. Часть 3

Рассмотрим, как Solidity поддерживает более сложные структуры данных. Массивы и сопоставления (mapping) на первый взгляд могут показаться знакомыми, но способ их реализации придает им радикально разные характеристики производительности.

https://medium.com/@hayeah/diving-into-the-ethereum-vm-the-hidden-costs-of-arrays-28e119f04a9b

Погружение в виртуальную машину Ethereum. Часть 4
Как расшифровать метод call смарт-контракта.

В этой статье увидим, как Solidity и EVM позволяют «внешним программам» использовать метод call контракта и вызывать изменение его состояния.

https://medium.com/@hayeah/how-to-decipher-a-smart-contract-method-call-8ee980311603

Погружение в виртуальную машину Ethereum. Часть 5
Процесс создания смарт-контракта.

Байт-код EVM, который мы видели до сих пор, прост: это просто инструкции, которые EVM выполняет сверху вниз, никакой магии в рукаве. Процесс создания контракта более увлекателен, поскольку он стирает барьер между кодом и данными.

https://medium.com/@hayeah/diving-into-the-ethereum-vm-part-5-the-smart-contract-creation-process-cb7b6133b855

Погружение в виртуальную машину Ethereum. Часть 6
Как реализуются Solidity Events.

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

https://blog.qtum.org/how-solidity-events-are-implemented-diving-into-the-ethereum-vm-part-6-30e07b3037b9
❤‍🔥4
Продолжаю написание простых смарт-контрактов, (одна из целей достичь 100 таких разборов)

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

https://teletype.in/@nodiggity/0DwiHIAksXx
3
Типы данных для чисел: uint, int

uint (unsigned integer) - это простой тип данных, который может хранить только положительные целые числа (числа без знака минус) в большинстве проектов используются строго положительные значения. int (integer) - простой тип данных, который может хранить как положительные, так и отрицательные целые числа. Целые числа возможно использовать в арифметических операциях, балансы токенов, счетчики циклов, последовательные идентификационные номера и т.д.

uint/int по умолчанию является аналогом uint256/int256.

В этом случае максимально допустимое число, для uint256 которое мы можем использовать при хранении данных будет равно 115792089237316195423570985008687907853269984665640564039457584007913129639935

256 это количество бит, которое мы выделяем для хранения данных числа. Бит – принимает только два значения 0 и 1 (т.е. 2 варианта) и теперь, когда мы 2 возводим в 256 степень (и вычитаем 1) мы получим максимально допустимое число (в размерности uint)

Самая минимальная размерность - это uint8, где минимальное допустимое число — это 0, max допустимое число 2 в 8 степени (и вычитаем 1) = (256-1) т.е. в uint8 public totalEligible; мы можем хранить максимум 255 человек (переменная из прошлого контракта)

Почему мы вычитаем 1? Важно понимать, что число 0 также занимает 1 бит информации, поэтому отчет начинается с нуля.

Каждая следующая размерность (начиная с uint8 ) увеличивается с шагом 8 т.е. следующая допустимая размерность для компиляции = uint16 -> uint24 -> uint32 и т.д до uint256

В int8 размерность высчитывается иначе т.к. для хранения информации о знаке (0 или 1) *если ноль, то число положительное, когда 1 то число отрицательное, всегда резервируется один бит (для битового знака) И для хранения числа у нас остается только 7 бит, а не 8 как в uint8. Поэтому мы возводим 2 (*помним что бит – принимает только два значения 0 и 1 т.е. 2 варианта) возводим в 7 степень и (вычитаем 1) = 127 максимально допустимое число. т.к. у нас есть информация об отрицательном знаке, то и минимальное значение уже не 0 , а (-128)

// переменные int*
int8 = От -128 до 127
int16 = От -32 768 до 32 767
int32 = От -2 147 483 648 до 2 147 483 647
int64 = От -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807
int128 = От -170141183460469231731687303715884105728 до 170141183460469231731687303715884105727
int256 = От -57896044618658097711785492504343953926634992332820282019728792003956564819968 до 57896044618658097711785492504343953926634992332820282019728792003956564819967
// переменные uint*
uint8 = От 0 до 255
uint16 = От 0 до 65 535
uint32 = От 0 до 4 294 967 295
uint64 = От 0 до 18 446 744 073 709 551 615
uint256 = От 0 до 115792089237316195423570985008687907853269984665640564039457584007913129639935


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

В Solidity в отличие от многих других языков, 256-битные целые числа без знака являются целочисленным типом «по умолчанию». Например, массивы возвращают uint256 целые числа для своего .length свойства:

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.23;

contract SignedUnsignedIntegers {
uint8[] public someArray = [0, 1, 2, 3, 4];

// Это не удастся скомпилировать
// Возвращает целое число, но someArray.length — это uint
function getLength1() public view returns(int){
return someArray.length;
}

// Это отлично!
function getLength2() public view returns(uint){
return someArray.length;
}

// тоже допустимо
function getLength3() public view returns(int){
return int(someArray.length);
}

}


Поскольку someArray.length возвращается как uint256, мы не можем вернуть его как int без явного преобразования типа, что мы делаем в getLength3(). Лучше избегать использования целых чисел со знаком, если этого не требует конкретной цели. В основном будет достаточно задавать тип данных uint.
3
Смарт-контракт «Калькулятор»

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

https://teletype.in/@nodiggity/81T5YzNnViQ
❤‍🔥2
Remix Release v0.40.0

1) Обновленный компилятор Vyper, который компилирует последнюю версию.

Remix объединился с ApeWorX для создания нового компилятора Vyper. Это означает, что вы можете компилировать контракты Vyper в Remix и использовать не только последнюю версию, но и почти все версии Vyper.

2) Поддержка Circom v2.1.6

В Remix теперь можно запустить последнюю версию компилятора Circom. Так что попробуйте свои эллиптические кривые с помощью новейшей разработки Circom.

3) Шаблоны Uniswap v4.

В окне "Create Workspace" выберите один из вариантов шаблонов Uniswap v4.

Файлы Uniswap v4 взяты с Cookbook.dev. Таким образом, плагин Remix COOKBOOK.DEV активируется и записывает файлы шаблонов в проводник.

HookBook MultiSigSwapHook.

Контракт MultiSigSwapHook можно использовать, если вы хотите убедиться, что операция обмена на Uniswap (или аналогичной платформе) была одобрена несколькими сторонами, прежде чем ее можно будет выполнить.

Uniswap v4 — это новый протокол автоматического маркет-мейкера, который обеспечивает расширяемость и возможность настройки пулов.

Uniswap v4 Hooks — отличный сайт, на котором можно увидеть, что было создано на данный момент.

v4-periphery - периферийные смарт-контракты для взаимодействия с Uniswap v4.

https://medium.com/remix-ide/remix-release-v0-40-0-17668192db64
2
Boom! Один из способов ознакомиться с уязвимостями и ошибками в смарт-контрактах на практике - это прохождение/решение CTF и начнем с основ игры Ethernaut.

https://teletype.in/@nodiggity/l2H2En0fEh0
👍2
Передайте мне ваши пароли, но не нужно их раскрывать. Вот мой:

[["0x0ac917308a7b8db29288c028109c8617f910f6b6843c975a736374ddbc276797","0x1960e7c91c4bb774e6308c418615d566280679a5b7c18b45f89ab64098187bdf"],[["0x2df5af53473f2343c58091e657898f92b5b7f0b88a6ea7514c9952a47e07f444","0x0121a9c22c2abafb7dee637ef4f743a4d62798835350d042d55c708d2481fe3d"],["0x1e46763eeacc9f85215740e3d3f7eaddc2fca5c8115c282be1e60157740bcec8","0x23d8a13843a39d8f37b8a0ef6da31281b7ee2c61d16079a48f2620b9f6ef9aa0"]],["0x2f990abf7f96daee712f3ca282e7e304370ac70d633b6156a41b91f140e6cb41","0x0e619f6d20fc6c39fa3fa0320c795e57218824f1f11ee90b0d7edf3a306de8fd"]]
🙈3