Loading...
Loading...
transfer(address,uint256)
Селектор функции (Function Selector) — первые 4 байта хеша сигнатуры функции, например keccak256("transfer(address,uint256)") = 0xa9059cbb
Calldata — данные, передаваемые при вызове функции
Межконтрактное взаимодействие — вызов функций одного контракта из другогоМетод | Описание | Когда использовать | Особенности |
|---|---|---|---|
abi.encode() | Стандартное ABI-кодирование | Для совместимости с внешними приложениями | Каждый элемент занимает 32 байта |
abi.encodePacked() | Компактное кодирование | Для экономии газа, хеширования | Минимальные размеры, нет паддинга |
abi.encodeWithSignature() | Кодирование с сигнатурой функции | Для вызова функций других контрактов | Включает селектор функции |
abi.encodeWithSelector() | Кодирование с готовым селектором | Когда селектор уже известен | Более эффективно чем encodeWithSignature |
abi.decode() | Декодирование ABI-данных | Для извлечения данных из bytes | Требует указания типов данных |
Исходные данные:uint8(42) = 0x2a (1 байт)uint16(1000) = 0x03e8 (2 байта)bool(true) = 0x01 (1 бит → 1 байт)
После паддинга (abi.encode):uint8(42) = 0x000000000000000000000000000000000000000000000000000000000000002a (32 байта)uint16(1000) = 0x00000000000000000000000000000000000000000000000000000000000003e8 (32 байта)bool(true) = 0x0000000000000000000000000000000000000000000000000000000000000001 (32 байта)
Итого: 96 байт вместо 4 байт исходных данныхcontract StringPaddingExample { function demonstrateStringPadding() public pure returns (bytes memory) { string memory text = "Hello"; // 5 символов = 5 байт
bytes memory encoded = abi.encode(text);
// Структура результата: // Байты 0-31: 0000000000000000000000000000000000000000000000000000000000000020 (указатель на данные) // Байты 32-63: 0000000000000000000000000000000000000000000000000000000000000005 (длина строки: 5) // Байты 64-95: 48656c6c6f000000000000000000000000000000000000000000000000000000 ("Hello" + паддинг) // ^^^^^^^^^^ (5 байт "Hello") // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (27 байт паддинга)
return encoded; }}
```soliditycontract AlexABIEncoding { function demonstrateEncode() public pure returns (bytes memory) { // Кодируем разные типы данных uint256 number = 42; // 32 байта string memory text = "Hello"; // 32 байта (указатель) + 32 байта (длина) + 32 байта (данные с паддингом) bool flag = true; // 32 байта
// Стандартное ABI-кодирование bytes memory encoded = abi.encode(number, text, flag);
return encoded; // Результат: ~128 байт (4 блока по 32 байта + данные строки) }
function encodeNumbers() public pure returns (bytes memory) { uint8 small = 42; // Занимает 1 байт, но кодируется в 32 байта uint16 medium = 1000; // Занимает 2 байта, но кодируется в 32 байта uint256 large = 123456; // Уже занимает 32 байта
return abi.encode(small, medium, large); // Результат: ровно 96 байт (3 × 32 байта) }
function encodeArray() public pure returns (bytes memory) { uint256[] memory numbers = new uint256[](3); numbers[0] = 100; numbers[1] = 200; numbers[2] = 300;
return abi.encode(numbers); // Структура: 32 байта (указатель) + 32 байта (длина) + 3×32 байта (элементы) // Итого: 160 байт }
function encodeStruct() public pure returns (bytes memory) { // Можем кодировать и сложные структуры string memory name = "Alex"; uint256 age = 25;
// Кодируем как отдельные элементы return abi.encode(name, age); // name: 32 байта (указатель) + 32 байта (длина) + 32 байта (данные) // age: 32 байта // Итого: 128 байт }}uint256(42) → 0x000000000000000000000000000000000000000000000000000000000000002abool(true) → 0x0000000000000000000000000000000000000000000000000000000000000001address(...) → 0x000000000000000000000000[20 байт адреса]string("Hello") →├── 0x0000000000000000000000000000000000000000000000000000000000000020 (метка типа данных)├── 0x0000000000000000000000000000000000000000000000000000000000000005 (длина)└── 0x48656c6c6f000000000000000000000000000000000000000000000000000000 ("Hello" + паддинг)contract PaddingComparison { function comparePackedVsStandard() public pure returns ( bytes memory withPadding, bytes memory withoutPadding, uint256 paddingSize, uint256 packedSize ) { uint8 small = 42; // 1 байт данных uint16 medium = 1000; // 2 байта данных uint32 large = 50000; // 4 байта данных
// С паддингом (abi.encode) withPadding = abi.encode(small, medium, large); // Размер: 96 байт (3 × 32 байта) // 0x000000000000000000000000000000000000000000000000000000000000002a // 00000000000000000000000000000000000000000000000000000000000003e8 // 000000000000000000000000000000000000000000000000000000000000c350
// Без паддинга (abi.encodePacked) withoutPadding = abi.encodePacked(small, medium, large); // Размер: 7 байт (1 + 2 + 4 байта) // 0x2a03e8c350 // ^^ = uint8(42) // ^^^^ = uint16(1000) // ^^^^^^^^ = uint32(50000)
paddingSize = withPadding.length; // 96 байт packedSize = withoutPadding.length; // 7 байт
return (withPadding, withoutPadding, paddingSize, packedSize); }
function showPackedStructure() public pure returns (bytes memory) { // Демонстрация того, как abi.encodePacked склеивает данные address user = 0x1234567890123456789012345678901234567890; // 20 байт uint256 amount = 1000; // 32 байта uint8 flag = 1; // 1 байт
bytes memory packed = abi.encodePacked(user, amount, flag);
// Структура результата (53 байта total): // Байты 0-19: 1234567890123456789012345678901234567890 (адрес, 20 байт) // Байты 20-51: 00000000000000000000000000000000000000000000000000000000000003e8 (amount, 32 байта) // Байт 52: 01 (flag, 1 байт)
return packed; // 53 байта вместо 96 байт с abi.encode }
function showStringPacking() public pure returns ( bytes memory standardEncoded, bytes memory packedEncoded ) { string memory text = "Hi"; // 2 символа
// С паддингом (abi.encode): 96 байт standardEncoded = abi.encode(text); // Структура: // 32 байта: указатель на данные // 32 байта: длина строки (2) // 32 байта: "Hi" + 30 байт паддинга
// Без паддинга (abi.encodePacked): 2 байта packedEncoded = abi.encodePacked(text); // Структура: просто "Hi" = 0x4869
return (standardEncoded, packedEncoded); }}Данные: uint8(42), string("Hi"), bool(true)
abi.encode (С ПАДДИНГОМ):┌─────────────────────────────────┐ 32 байта│ 00000000000000000000000000000002a │ uint8(42) + паддинг├─────────────────────────────────┤ 32 байта│ 00000000000000000000000000000020 │ указатель на строку├─────────────────────────────────┤ 32 байта│ 00000000000000000000000000000001 │ bool(true) + паддинг├─────────────────────────────────┤ 32 байта│ 00000000000000000000000000000002 │ длина строки├─────────────────────────────────┤ 32 байта│ 4869000000000000000000000000000 │ "Hi" + паддинг└─────────────────────────────────┘Итого: 160 байт
abi.encodePacked (БЕЗ ПАДДИНГА):┌──┬────┬──┐│2a│4869│01│ uint8(42) + "Hi" + bool(true)└──┴────┴──┘Итого: 5 байт (экономия 97%!)contract AlexPackedEncoding { function compareEncodings() public pure returns ( bytes memory standard, bytes memory packed, uint256 standardLength, uint256 packedLength ) { uint8 a = 1; // 1 байт данных uint16 b = 2; // 2 байта данных
// Стандартное кодирование: каждое число занимает 32 байта standard = abi.encode(a, b); // 64 байта (32 + 32)
// Компактное кодирование: минимальное количество байтов packed = abi.encodePacked(a, b); // 3 байта (1 + 2)!
return (standard, packed, standard.length, packed.length); }
function demonstratePackedAdvantage() public pure returns ( bytes memory result, uint256 gasUsed ) { uint8 smallNumber = 42; // 1 байт uint16 mediumNumber = 1000; // 2 байта uint32 biggerNumber = 50000; // 4 байта
uint256 gasBefore = gasleft();
// С abi.encodePacked экономим место и газ result = abi.encodePacked(smallNumber, mediumNumber, biggerNumber); // Всего 7 байт вместо 96 байт!
gasUsed = gasBefore - gasleft(); return (result, gasUsed); }
function createHash() public pure returns (bytes32) { // Популярное использование: создание хешей string memory message = "Hello"; uint256 timestamp = 1234567890; address user = 0x1234567890123456789012345678901234567890;
// Компактно кодируем и хешируем return keccak256(abi.encodePacked(message, timestamp, user)); // Экономия: message (без паддинга) + 32 байта + 20 байт = ~57 байт // Вместо: 96 байт (указатель) + 64 байт (длина и данные) + 64 байт = 224 байта }
function demonstrateStringPacking() public pure returns ( bytes memory packed, bytes memory standard ) { string memory short = "Hi"; // 2 символа
packed = abi.encodePacked(short); // 2 байта: 0x4869 standard = abi.encode(short); // 96 байт с указателями и паддингом
return (packed, standard); }
function arrayPackingExample() public pure returns (bytes memory) { uint8[] memory numbers = new uint8[](4); numbers[0] = 10; numbers[1] = 20; numbers[2] = 30; numbers[3] = 40;
// abi.encodePacked просто склеивает байты return abi.encodePacked(numbers); // 4 байта: 0x0a141e28 // abi.encode создал бы 160 байт (указатель + длина + 4×32 байта) }}// abi.encode(42, 1000):0x000000000000000000000000000000000000000000000000000000000000002a // uint8(42) в 32 байтах0x00000000000000000000000000000000000000000000000000000000000003e8 // uint16(1000) в 32 байтах// Итого: 64 байта
// abi.encodePacked(42, 1000):0x2a03e8 // uint8(42) в 1 байте + uint16(1000) в 2 байтах// Итого: 3 байтаХарактеристика | abi.encode | abi.encodePacked |
|---|---|---|
Соответствие стандарту ABI | ✅ Полностью совместимо | ❌ НЕ совместимо |
Размер результата | Больше (с паддингом) | Меньше (без паддинга) |
Возможность декодирования | ✅ Можно декодировать | ❌ Нельзя декодировать |
Газ | Больше (больше данных) | Меньше (меньше данных) |
Безопасность | Высокая (нет коллизий) | Риск коллизий |
Использование в хешах | Можно, но неэффективно | ✅ Идеально |
Межконтрактные вызовы | ✅ Стандарт | ❌ Не подходит |
Работа с внешними приложениями | ✅ Стандарт | ❌ Не подходит |
// Примеры использования abi.encodefunction callExternalContract(address target, uint256 amount) external { bytes memory data = abi.encodeWithSignature("transfer(address,uint256)", msg.sender, amount); (bool success,) = target.call(data); require(success, "Transfer failed");}
function storeUserData(string memory name, uint256 age) external { bytes memory userData = abi.encode(name, age, block.timestamp); // Сохраняем в mapping для последующего декодирования userDataStorage[msg.sender] = userData;}// Примеры использования abi.encodePackedfunction createUniqueId(address user, uint256 nonce) external pure returns (bytes32) { return keccak256(abi.encodePacked(user, nonce, block.timestamp));}
function generateSignature(string memory message, uint256 timestamp) external pure returns (bytes32) { return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", message, timestamp));}
function createCommitment(uint256 secret, uint256 nonce) external pure returns (bytes32) { return keccak256(abi.encodePacked(secret, nonce));}abi.encode и abi.encodePacked — это не просто вопрос производительности, но и безопасности!abi.encodePacked с динамическими типами (строки, массивы), разные комбинации входных данных могут дать одинаковый хеш. Это называется "коллизия хешей" и может привести к серьезным уязвимостям в вашем контракте.// ❌ ОПАСНО: Возможны коллизии хешейfunction unsafeCommitment(string[] memory tokens, uint256[] memory amounts) external pure returns (bytes32) { return keccak256(abi.encodePacked(tokens, amounts)); // Проблема: ["AB", "CD"] и ["A", "BCD"] дадут одинаковый хеш!}
// ✅ БЕЗОПАСНО: Используйте abi.encode для критических хешейfunction safeCommitment(string[] memory tokens, uint256[] memory amounts) external pure returns (bytes32) { return keccak256(abi.encode(tokens, amounts)); // Каждый элемент четко разделен, коллизии невозможны}abi.encodePacked просто не будет работать.// ✅ ПРАВИЛЬНО: Стандартный способ вызова функцийfunction transferTokens(address tokenContract, address to, uint256 amount) external { bytes memory data = abi.encodeWithSignature("transfer(address,uint256)", to, amount); (bool success,) = tokenContract.call(data); require(success, "Transfer failed");}
// ❌ НЕПРАВИЛЬНО: abi.encodePacked не совместимо с ABIfunction wrongTransfer(address tokenContract, address to, uint256 amount) external { bytes memory data = abi.encodePacked("transfer", to, amount); // Не сработает! tokenContract.call(data);}abi.encodePacked нет разделителей между элементами, разные комбинации входных данных могут дать одинаковый результат.contract SecurityExample { // ❌ УЯЗВИМОСТЬ: Hash collision attack из-за отсутствия паддинга function vulnerableHash(string memory a, string memory b) external pure returns (bytes32) { return keccak256(abi.encodePacked(a, b)); // "AB" + "CD" = "ABCD" (склеивается без разделителей) // "A" + "BCD" = "ABCD" (тот же результат!) // Нет паддинга → нет границ между элементами → коллизия! }
// Демонстрация проблемы паддинга function showCollisionProblem() external pure returns ( bytes memory case1, bytes memory case2, bool collision ) { // Случай 1: две короткие строки string memory str1a = "AB"; string memory str1b = "CD"; case1 = abi.encodePacked(str1a, str1b); // = "ABCD"
// Случай 2: одна строка + другая строка string memory str2a = "A"; string memory str2b = "BCD"; case2 = abi.encodePacked(str2a, str2b); // = "ABCD" (та же строка!)
// Без паддинга нельзя определить границы collision = keccak256(case1) == keccak256(case2); // true!
return (case1, case2, collision); }
// Почему abi.encode решает эту проблему function showHowPaddingSolves() external pure returns ( bytes memory case1, bytes memory case2, bool collision ) { string memory str1a = "AB"; string memory str1b = "CD"; case1 = abi.encode(str1a, str1b); // Структура: [указатель1][указатель2][длина1][данные1+паддинг][длина2][данные2+паддинг]
string memory str2a = "A"; string memory str2b = "BCD"; case2 = abi.encode(str2a, str2b); // Структура: [указатель1][указатель2][длина1][данные1+паддинг][длина2][данные2+паддинг] // Разные длины → разная структура → разные хеши
collision = keccak256(case1) == keccak256(case2); // false - безопасно!
return (case1, case2, collision); }
// ✅ БЕЗОПАСНО: Использование разделителя function safeHashWithSeparator(string memory a, string memory b) external pure returns (bytes32) { return keccak256(abi.encodePacked(a, "|", b)); // "AB|CD" ≠ "A|BCD" - разные результаты }
// ✅ БЕЗОПАСНО: Использование abi.encode function safeHashWithEncode(string memory a, string memory b) external pure returns (bytes32) { return keccak256(abi.encode(a, b)); // Паддинг предотвращает коллизии }
// ✅ БЕЗОПАСНО: Добавление длин строк function safeHashWithLengths(string memory a, string memory b) external pure returns (bytes32) { return keccak256(abi.encodePacked( bytes(a).length, a, bytes(b).length, b )); }}// Примеры правильного использования abi.encodebytes memory callData = abi.encodeWithSignature("vote(uint256,bool)", proposalId, support);bytes32 voteHash = keccak256(abi.encode(voter, proposalId, support, nonce));bytes memory userData = abi.encode(name, age, preferences); // Для последующего декодирования// Примеры правильного использования abi.encodePackedbytes32 uniqueId = keccak256(abi.encodePacked(user, block.timestamp, nonce));bytes32 messageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));bytes memory signature = abi.encodePacked(r, s, v); // Собираем подписьabi.encodePacked, создайте тесты с разными комбинациями данных, которые могут дать одинаковый результатabi.encodePacked с строками вставляйте символы вроде "|" или добавляйте длины строк для четкого разделения// Пример безопасного дизайнаcontract SafeHashingExample { mapping(bytes32 => bool) public usedHashes; uint256 public nonce;
function createSafeHash(address user, uint256 amount) external returns (bytes32) { // Используем nonce для уникальности nonce++;
// Безопасное хеширование с abi.encode bytes32 hash = keccak256(abi.encode( user, amount, block.timestamp, nonce, address(this) // Адрес контракта для уникальности ));
// Проверяем на дубликаты (дополнительная защита) require(!usedHashes[hash], "Hash already used"); usedHashes[hash] = true;
return hash; }}// Межконтрактные вызовы → abi.encodeWithSignature/abi.encode (с паддингом)bytes memory data = abi.encodeWithSignature("transfer(address,uint256)", to, amount);
// Хеширование с безопасностью → abi.encode (паддинг предотвращает коллизии)bytes32 hash = keccak256(abi.encode(user, amount, nonce));
// Хеширование с экономией газа → abi.encodePacked (БЕЗ паддинга, только фиксированные типы!)bytes32 id = keccak256(abi.encodePacked(user, block.timestamp, nonce));
// Декодирование → abi.decode (работает только с данными abi.encode, где есть паддинг)(address user, uint256 amount) = abi.decode(data, (address, uint256));Аспект | abi.encode (С паддингом) | abi.encodePacked (БЕЗ паддинга) |
|---|---|---|
Размер данных | Больше (каждый элемент 32 байта) | Меньше (минимальный размер) |
Структура | Предсказуемая, четкие границы | Плотная упаковка, нет границ |
Безопасность | Высокая (паддинг разделяет элементы) | Риск коллизий без разделителей |
Декодирование | Возможно (известны границы элементов) | Невозможно (границы потеряны) |
Использование | Стандарт для всех внешних взаимодействий | Только для внутренней оптимизации |


