آنچه در این مقاله میخوانید [پنهانسازی]
پاسخ کوتاه: abi.encode داده ها را طبق استاندارد ABI با بلوک های 32 بایتی و شامل طول برای انواع پویا کدگذاری می کند و خروجی آن همیشه قابل decode است؛ در مقابل abi.encodePacked همان داده ها را فشرده و بدون پدینگ و بدون ذخیره طول به هم می چسباند. نتیجه فشرده معمولا کوچک تر و ارزان تر است، اما در ترکیب چند مقدار پویا می تواند مبهم و منجر به برخورد هش شود. برای keccak256 وقتی بیش از یک آرگومان پویا دارید از abi.encode استفاده کنید؛ در حالت آرگومان های فقط ثابت یا یک آرگومان پویا، abi.encodePacked مناسب است.
تعریف و تفاوت های کلیدی
هر دو تابع مجموعه ای از مقادیر را به bytes تبدیل می کنند، اما هدف و قالب خروجی متفاوت است:
| ویژگی | abi.encode | abi.encodePacked |
|---|---|---|
| قالب | استاندارد ABI (هد و تیل، پدینگ 32 بایت) | فشرده و چسبانده شده، بدون پدینگ و طول |
| انواع پویا (string, bytes, T[]) | طول و اشاره گر/داده ها به شکل استاندارد | فقط داده خام کنار هم، بدون طول |
| قابلیت decode عمومی | بله، با ABI Decoder | خیر، عموما غیرقابل بازگشایی مطمئن |
| کاربرد اصلی | فراخوانی توابع، ذخیره و تبادل داده قابل اعتماد | هش گیری و چسباندن سریع داده، ساخت کلیدها |
| ریسک ابهام/برخورد | خیلی کم (طراحی شده برای رفع ابهام) | ممکن است، به ویژه با بیش از یک آرگومان پویا |
| اندازه/هزینه گس | بزرگ تر به دلیل پدینگ | کوچک تر، معمولا ارزان تر |
چه زمانی از هرکدام استفاده کنیم؟
- فراخوانی توابع، امضای ساختارها و تبادل بین زنجیره و خارج از زنجیره: abi.encode یا مشتقات آن مانند abi.encodeWithSelector. خروجی سازگار و قابل decode نیاز است.
- ساخت شناسه و هش یکتا با keccak256:
- اگر فقط انواع ثابت (uint، address، bytes32 و…) یا فقط یک مقدار پویا دارید، abi.encodePacked کافی و کم هزینه است.
- اگر بیش از یک آرگومان پویا دارید (مثلا دو string)، از abi.encode استفاده کنید یا قبل از encodePacked هر مقدار پویای جداگانه را keccak256 کنید.
- چسباندن سریع بایت ها بدون نیاز به decode: abi.encodePacked.
- نیاز به decode امن در آینده یا خارج از زنجیره: همیشه abi.encode.
مثال های عملی سالیدیتی
کد زیر تفاوت اندازه خروجی، سناریوی برخورد با packed و روش های امن تر برای هش را نشان می دهد. این کد برای آموزش است.
pragma solidity ^0.8.20;
contract AbiEncodingExamples {
// مقایسه خروجی encode و encodePacked
function encodeVsPacked()
external
pure
returns (bytes memory a, bytes memory b)
{
a = abi.encode(
uint8(1),
address(0x1111111111111111111111111111111111111111)
);
b = abi.encodePacked(
uint8(1),
address(0x1111111111111111111111111111111111111111)
);
}
// طول خروجی ها برای درک تفاوت پدینگ
function lengths()
external
pure
returns (uint256 lenEncode, uint256 lenPacked)
{
bytes memory a = abi.encode(
uint8(1),
address(0x1111111111111111111111111111111111111111)
);
bytes memory b = abi.encodePacked(
uint8(1),
address(0x1111111111111111111111111111111111111111)
);
lenEncode = a.length; // انتظار 64 بایت (دو اسلات 32 بایتی)
lenPacked = b.length; // انتظار 21 بایت (1 + 20)
}
// برخورد محتمل با encodePacked روی چند رشته پویا
function hashCollisionPacked() external pure returns (bool) {
// این دو هش برابر می شوند
return
keccak256(abi.encodePacked("ab", "c")) ==
keccak256(abi.encodePacked("a", "bc"));
}
// نبود برخورد با abi.encode به دلیل طول و ساختار استاندارد
function hashNoCollisionEncode() external pure returns (bool) {
// این دو هش برابر نیستند
return
keccak256(abi.encode("ab", "c")) ==
keccak256(abi.encode("a", "bc"));
}
// ساخت شناسه سفارش به شکل امن با abi.encode
function safeOrderId(
address user,
uint256 amount,
string memory memo
) external pure returns (bytes32) {
return keccak256(abi.encode(user, amount, memo));
}
// گزینه بهینه تر برای کم کردن داده پویا در هش نهایی
function saferOrderId(
address user,
uint256 amount,
string memory memo
) external pure returns (bytes32) {
// تبدیل رشته به هش ثابت طول، سپس encode
return keccak256(abi.encode(user, amount, keccak256(bytes(memo))));
}
}
چگونه نتیجه را بررسی کنیم:
- در Remix تابع lengths را صدا بزنید. lenEncode باید 64 و lenPacked باید 21 باشد.
- تابع hashCollisionPacked باید true برگرداند که نشان دهنده برخورد در packed است.
- تابع hashNoCollisionEncode باید false برگرداند که نشان می دهد abi.encode ابهام را از بین می برد.
- برای ساخت شناسه، خروجی safeOrderId یا saferOrderId یک bytes32 پایدار و بدون ابهام خواهد بود.
چگونه برخورد و ابهام را از بین ببریم؟
- قانون ساده: اگر بیش از یک مقدار پویا دارید، از keccak256(abi.encode(…)) استفاده کنید.
- اگر مجبورید abi.encodePacked استفاده کنید، هر مقدار پویا را قبل از آن keccak256 کنید تا به bytes32 ثابت تبدیل شود:
bytes32 h = keccak256( abi.encodePacked( user, amount, keccak256(bytes(memo)), keccak256(bytes(note)) ) ); - جداکننده یا طول صریح اضافه کنید تا مرزها مشخص شوند (مثلا یک بایت نوع یا طول قبل از هر رشته).
- تا حد امکان از انواع ثابت طول استفاده کنید (مثلا bytes32 به جای string های کوتاه ثابت).
همسان سازی با کد خارج از زنجیره
برای تولید همان بایت ها یا هش در کد خارج از زنجیره، باید نوع ها و ترتیب دقیقا یکسان باشد:
- معادل abi.encode در بیشتر کتابخانه ها رمزگذار استاندارد ABI است (encoder استاندارد).
- معادل abi.encodePacked در برخی ابزارها با نام هایی شبیه solidityPack یا packed encoding شناخته می شود. برای تولید keccak256 متناظر، تابعی شبیه solidityKeccak256 استفاده می شود.
- توجه کنید که string و bytes در packed بدون طول هستند؛ اگر خارج از زنجیره طول را اضافه کنید نتیجه متفاوت می شود.
- در مقایسه هش ها، اختلاف حتی در یک بایت از نوع یا ترتیب ورودی ها، هش نهایی را کاملا تغییر می دهد.
نکات و خطاهای رایج
- استفاده از abi.encodePacked برای فراخوانی توابع یا داده ای که باید بعدا decode شود اشتباه است. این خروجی استاندارد ABI نیست.
- ترکیب بیش از یک string یا bytes با abi.encodePacked می تواند به برخورد هش منجر شود. برای این حالت از abi.encode یا هش کردن جداگانه هر جزء استفاده کنید.
- انتظار برابری مستقیم بین bytes خروجی encode و encodePacked اشتباه است؛ اولی پدینگ و طول دارد، دومی ندارد.
- در packed، address دقیقا 20 بایت است و bool یک بایت. اگر در طرف مقابل 32 بایت انتظار داشته باشید نتیجه نادرست می شود.
- در آرایه های پویا، packed فقط داده عناصر را می چسباند و طول آرایه درج نمی شود. بدون قرارداد قبلی در مورد قالب، decode امن ممکن نیست.
گام بعدی
اگر در آغاز مسیر سالیدیتی هستید و می خواهید مباحث ABI، هش گیری و الگوهای ایمن را اصولی بیاموزید، شرکت در یک دوره آموزش سالیدیتی مسیر یادگیری شما را سریع تر و منسجم تر می کند.
قاعده طلایی را به خاطر بسپارید: زمانی که یکتایی و قابل decode بودن اهمیت دارد از abi.encode استفاده کنید؛ زمانی که فقط به چسباندن سریع برای هش نیاز دارید و ورودی مبهم نیست، abi.encodePacked انتخاب مناسبی است.







