Loading...
Loading...
Особенности памяти EVM:- Линейная структура (как массив байтов)- Расширяется по мере необходимости- Очищается после завершения транзакции- Стоимость растет квадратично с размером
Хранилище контракта:- Постоянное хранение данных- Ключ-значение (каждый 32 байта)- Очень дорогие операции записи- Данные сохраняются между вызовами
// Solidity кодfunction add(uint256 a, uint256 b) public pure returns (uint256) { return a + b;}
// Байт-код EVM (упрощенно)PUSH1 0x04 // Загрузить offset данныхCALLDATALOAD // Загрузить первый параметр (a)PUSH1 0x24 // Загрузить offset второго параметраCALLDATALOAD // Загрузить второй параметр (b)ADD // Сложить a + bPUSH1 0x00 // Подготовить возвратMSTORE // Сохранить результат в памятьPUSH1 0x20 // Размер возвращаемых данныхPUSH1 0x00 // Offset в памятиRETURN // Вернуть результат
ADD // СложениеSUB // ВычитаниеMUL // УмножениеDIV // ДелениеMOD // Остаток от деления
AND // Логическое ИOR // Логическое ИЛИXOR // Исключающее ИЛИNOT // Логическое НЕ
LT // Меньше чемGT // Больше чемEQ // РавноISZERO // Равно нулю
MLOAD // Загрузить из памятиMSTORE // Сохранить в памятьSLOAD // Загрузить из хранилищаSSTORE // Сохранить в хранилище
Стоимость операций (в газе):ADD, SUB, MUL: 3 газаDIV, MOD: 5 газаSLOAD (чтение хранилища): 800 газаSSTORE (запись в хранилище): 20,000 газа (новое значение)CALL (вызов другого контракта): 700+ газа
Структура EOA:- Balance: количество ETH- Nonce: счетчик транзакций- Нет кода- Нет хранилища
Пример:Address: 0x742d35Cc6634C0532925a3b8D4C9db96590c6C87Balance: 5.5 ETHNonce: 42
Структура Contract Account:- Balance: количество ETH- Nonce: счетчик создания контрактов- Code: байт-код контракта- Storage: постоянные данные
Пример:Address: 0x1234567890123456789012345678901234567890Balance: 0 ETHCode: 0x608060405234801561001057600080fd5b50...Storage: { 0x00: 0x48656c6c6f20576f726c64 // "Hello World" 0x01: 0x000000000000000000000000000000000000000000000000000000000000007b // 123}
// Пользователь вызывает функцию контрактаconst transaction = { to: '0x1234567890123456789012345678901234567890', data: '0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b8d4c9db96590c6c87000000000000000000000000000000000000000000000000000000000000000a', gas: 100000, gasPrice: 20000000000,};
Контекст выполнения включает:- Стек (пустой в начале)- Память (пустая в начале)- Доступ к хранилищу контракта- Информация о транзакции (отправитель, газ, данные)- Информация о блоке (номер, timestamp, difficulty)
Пример выполнения:1. PUSH1 0x60 // Положить 96 на стек Стек: [96] Газ: 3
2. PUSH1 0x40 // Положить 64 на стек Стек: [64, 96] Газ: 3
3. MSTORE // Сохранить 96 по адресу 64 в памяти Стек: [] Память[64] = 96 Газ: 3
Успешное завершение:- RETURN: возвращает данные- STOP: завершает без возврата данных
Ошибки:- REVERT: отменяет изменения, возвращает газ- Исключение: отменяет изменения, газ не возвращается- Out of Gas: не хватило газа для завершения
Особенности BSC:- Время блока: ~3 секунды- Газ: в разы дешевле Ethereum- Консенсус: Proof of Staked Authority- Совместимость: 100% EVM
Особенности Polygon:- Время блока: ~2 секунды- Газ: очень дешевый (доли цента)- Мосты с Ethereum- Активная экосистема DeFi
Особенности Avalanche:- Время финализации: <1 секунды- Высокая пропускная способность- Уникальный консенсус Avalanche- Полная EVM-совместимость
Возможности Remix Debugger:- Пошаговое выполнение- Просмотр стека и памяти- Анализ расхода газа- Трассировка вызовов
Функции Tenderly:- Анализ реальных транзакций- Визуализация вызовов контрактов- Симуляция транзакций- Мониторинг контрактов
Информация в Etherscan:- Трассировка вызовов- Изменения состояния- События (logs)- Расход газа по операциям
// Проблема: бесконечный циклfunction badFunction() public { while(true) { // Потратит весь газ! // некоторый код }}
// Решение: ограничить количество итерацийfunction goodFunction(uint256 iterations) public { require(iterations <= 100, "Too many iterations"); for(uint256 i = 0; i < iterations; i++) { // некоторый код }}
// Проблема: слишком много локальных переменныхfunction tooManyVariables() public { uint256 a = 1; uint256 b = 2; // ... еще 15 переменных uint256 q = 17; // Stack too deep!}
// Решение: использовать структурыstruct Variables { uint256 a; uint256 b; // ... другие переменные}
function betterFunction() public { Variables memory vars; vars.a = 1; vars.b = 2;}
// Уязвимый контрактcontract Vulnerable { mapping(address => uint256) public balances;
function withdraw() public { uint256 amount = balances[msg.sender]; (bool success,) = msg.sender.call{value: amount}(""); require(success); balances[msg.sender] = 0; // Слишком поздно! }}
// Защищенный контрактcontract Secure { mapping(address => uint256) public balances; bool private locked;
modifier noReentrancy() { require(!locked, "Reentrant call"); locked = true; _; locked = false; }
function withdraw() public noReentrancy { uint256 amount = balances[msg.sender]; balances[msg.sender] = 0; // Сначала изменяем состояние (bool success,) = msg.sender.call{value: amount}(""); require(success); }}
// Неэффективно: каждая переменная занимает 32 байтаstruct Inefficient { uint8 a; // 32 байта uint8 b; // 32 байта uint8 c; // 32 байта}
// Эффективно: все переменные в одном блокеstruct Efficient { uint8 a; // \ uint8 b; // > 32 байта всего uint8 c; // /}
// Для счетчиков часто достаточно uint32uint32 public counter; // Экономит газ при упаковке
// Для булевых флаговbool public isActive;
// Для адресов всегда используйте addressaddress public owner;
// Неэффективно: много записей в хранилищеfunction inefficient(uint256[] memory values) public { for(uint256 i = 0; i < values.length; i++) { someMapping[i] = values[i]; // Каждая запись стоит 20,000 газа }}
// Эффективно: минимизируем записиfunction efficient(uint256[] memory values) public { uint256 length = values.length; for(uint256 i = 0; i < length; i++) { // Кешируем length someMapping[i] = values[i]; }}
// Вместо множества вызововfunction batchTransfer( address[] memory recipients, uint256[] memory amounts) public { for(uint256 i = 0; i < recipients.length; i++) { transfer(recipients[i], amounts[i]); }}
// Вычисляем только когда нужноmapping(address => uint256) private _cachedValues;mapping(address => bool) private _cached;
function expensiveCalculation(address user) public view returns (uint256) { if (_cached[user]) { return _cachedValues[user]; }
// Дорогие вычисления только при первом обращении uint256 result = /* сложные вычисления */; return result;}
Преимущества EOF:- Валидация кода при деплое- Разделение кода и данных- Улучшенная безопасность- Оптимизация производительности
Verkle Trees vs Merkle Trees:- Меньший размер доказательств- Быстрее верификация- Поддержка stateless клиентов
Новые применения:- WASM-EVM для веб-приложений- EVM в облачных вычислениях- Интеграция с традиционными системами