Solidity abi.encode vs abi.encodePacked: Differences and Collision Risks
Compare Solidity abi.encode and abi.encodePacked, see why packed dynamic values can collide, and choose safer encoding for hashes and contract calls.

Two Solidity expressions can hash different-looking inputs to the same value even when Keccak-256 has not been broken. The surprise often comes from the bytes fed into the hash, not the hash function itself. If a contract uses those bytes for authorization, allowlists, or replay protection, an encoding shortcut can become a security boundary.
This guide connects that problem to blockchain basics and compares abi.encode, abi.encodePacked, abi.encodeCall, and explicit concatenation. It is technical education, not financial advice, and code handling assets should receive independent review and testing.
What Are abi.encode and abi.encodePacked?
Both functions turn Solidity values into bytes, but they preserve different information.
Think of shipping two items. Standard ABI encoding puts each item in a labeled, fixed-grid container and records where variable-size cargo begins. Packed encoding removes much of that spacing and metadata to make one compact stream. The compact parcel can be useful, but without separators you may not know where one flexible item ends and the next begins.
abi.encode(...) uses the standard ABI format. Static values occupy 32-byte words. Dynamic values such as string, bytes, and dynamic arrays use offsets and length information, followed by their data. Because the decoder also knows the expected types, abi.decode can reverse a valid standard encoding.
abi.encodePacked(...) uses Solidity's non-standard packed mode. Direct arguments shorter than 32 bytes are generally concatenated without normal padding, while dynamic values are placed in-line without their length. Structs and nested arrays are unsupported, and Solidity provides no matching packed decoder because boundaries can be ambiguous.
The Difference at a Glance
| Question | abi.encode | abi.encodePacked |
|---|---|---|
| Standard ABI layout? | Yes | No, non-standard packed mode |
| Keeps dynamic lengths and offsets? | Yes | No |
Reversible with abi.decode? | Yes, with matching types | No general decoder |
| Suitable for ordinary call arguments? | Yes | No |
| Compact for selected byte construction? | Less compact | Often compact |
| Safe with multiple dynamic values in an authentication hash? | Preserves boundaries | Ambiguous; avoid |
Compact does not automatically mean cheaper overall. Gas depends on where encoding runs, whether the result is copied, hashed, stored, or sent as calldata, and what the optimizer can remove. Measure the complete operation instead of treating packed encoding as a universal optimization.
Why Packed Dynamic Values Can Collide
The Solidity ABI specification gives the essential warning: packed encoding becomes ambiguous when two dynamically sized elements appear because their lengths are omitted.
bytes memory x = abi.encodePacked("a", "bc");
bytes memory y = abi.encodePacked("ab", "c");
assert(keccak256(x) == keccak256(y));Both byte streams are simply abc. This is an encoding collision, not a cryptographic collision: the inputs to Keccak-256 are already identical. Moving bytes across the invisible boundary creates another logical tuple with the same packed representation.
The risk becomes concrete when a contract treats this hash as proof of permission:
function digest(string memory role, string memory action)
external
pure
returns (bytes32)
{
return keccak256(abi.encodePacked(role, action)); // unsafe boundary
}If an attacker can influence both dynamic fields, the contract may authenticate a tuple different from the one a signer or administrator intended. Adding a visible delimiter is fragile unless the contract strictly forbids that delimiter inside every field and applies one unambiguous escaping rule. Standard encoding is usually the clearer fix:
return keccak256(abi.encode(role, action));The official documentation advises that when packed data protects signatures, authentication, or integrity, developers should keep the types fixed and allow at most one dynamic argument. Unless there is a compelling reason, it recommends abi.encode.
Static Types Need Care Too
Packed encoding retains less type-shaped structure even without strings. For direct arguments, values such as uint16 use their short representation rather than a full 32-byte word. Explicit casts can therefore change the byte stream:
abi.encodePacked(uint16(0x12)) // 0x0012
abi.encodePacked(uint256(0x12)) // 32-byte representationProtocol code must define exact types and ordering. Do not let an off-chain service guess whether a number was intended as uint16, uint256, or raw bytes. Cross-language tests should compare the final hexadecimal bytes, not merely the human-readable values.
Which Encoding Function Should You Use?
Hashing structured values
Prefer keccak256(abi.encode(...)) when tuple boundaries matter. For signed structured messages, use an established scheme such as EIP-712 typed-data signing, which includes type hashes and domain separation. EIP-712 itself notes that it does not provide replay protection automatically, so nonce, deadline, chain, contract, and application context still need deliberate design.
Building external contract calls
Prefer abi.encodeCall(functionPointer, (...)) when you have a typed function pointer. Solidity documents that it performs full type checking and produces the selector plus standard-encoded arguments. abi.encodeWithSelector and abi.encodeWithSignature can also construct calls, but they provide less compile-time assurance; a misspelled signature string can silently produce the wrong selector.
Our Ethereum calldata guide explains how the four-byte selector and standard ABI arguments fit inside transaction input.
Concatenating bytes or strings
Use bytes.concat(...) or string.concat(...) when concatenation is the actual intent. Those APIs communicate the goal more clearly than borrowing an encoding function. Still define separators or framing if the result will later be split into multiple fields.
Compact protocol fields
Packed bytes can be appropriate when a protocol specifies an exact fixed-width layout or only one dynamic tail. Document each byte range, validate lengths, and create test vectors for every supported type. Never assume another library reproduces Solidity's packed rules without checking its documentation and output.
Security Checklist
- List every encoded field in order, including its exact Solidity type.
- Mark each
string,bytes, and dynamic array as dynamic. - Reject
abi.encodePackedwhen two attacker-controlled dynamic values share an authentication or integrity hash. - Include domain context—such as chain ID, verifying contract, action, nonce, and deadline—when the threat model requires it.
- Prefer
abi.encodeCallfor typed call construction. - Compare Solidity and off-chain byte vectors in tests.
- Add adversarial cases that move bytes across field boundaries.
- Have authorization and asset-moving code reviewed independently.
Risks and Common Mistakes
- Confusing encoding collisions with hash collisions: identical packed bytes naturally produce identical hashes; Keccak-256 need not fail.
- Hashing without domain separation: an unambiguous tuple can still be replayed in another contract, chain, or action context if the design omits scope.
- Treating a selector as a complete call: contract calldata needs a four-byte selector plus standard ABI-encoded arguments.
- Assuming packed bytes are decodable: dynamic boundaries and lengths may be gone.
- Optimizing before measuring: smaller intermediate bytes do not prove lower end-to-end gas.
- Trusting one happy-path test: boundary manipulation, empty strings, maximum values, arrays, and cross-language output need coverage.
- Using encoding correctness as an audit substitute: access control, signature recovery, nonce consumption, expiry, upgradeability, and external calls remain separate risks.
Smart-contract errors can cause irreversible loss. Test on a local network or testnet, use maintained tooling, limit exposed value, and do your own research (DYOR).
FAQ
Is abi.encodePacked deprecated?
The current Solidity documentation still defines it. There has been public discussion about removing it in a future breaking release, but an open discussion is not a released language change. Check the documentation for the exact compiler version you use.
Can I safely pack two addresses?
Two fixed-width address values have identifiable widths when the protocol fixes their order. That avoids the specific missing-length ambiguity of two dynamic strings. You still need domain separation, fixed types, and agreement with off-chain encoders if the hash authorizes anything.
Does adding a separator make two dynamic strings safe?
Only if the framing is provably unambiguous, including escaping and validation. In security-sensitive code, standard ABI encoding or a reviewed typed-data standard is easier to reason about.
Why is there no abi.decodePacked?
Packed encoding may omit padding, offsets, and dynamic lengths. Multiple original tuples can produce the same bytes, so a general inverse cannot know the intended boundaries.
Primary Sources
Sources accessed September 2, 2026:
- Solidity Contract ABI Specification
- Solidity ABI encoding and decoding functions
- Ethereum.org: Interacting with smart contracts
- EIP-712: Typed structured data hashing and signing
Choose the representation by the boundary you must preserve. Standard ABI encoding favors structure and reversibility; packed encoding favors compact byte construction under strict constraints. This article is educational and not financial advice (NFA).
Keep learning

Ethereum Calldata Explained: How to Decode Transaction Input Data
Learn how Ethereum calldata encodes function selectors and arguments, how explorers decode it, and what to verify before signing a contract transaction.

EIP-712 Typed Data Signing: What Your Wallet Is Asking You to Approve
EIP-712 typed data signing makes off-chain messages structured and readable. Learn domains, replay risks, and what to check before signing.

What Are Smart Contracts? How They Work and Real Use Cases
Discover what smart contracts are, how they work on the blockchain, and their real-world use cases in DeFi, NFTs, and RWA — plus risks, limits, and FAQ.
Explore related topics
Crypto Address Poisoning: How to Verify Wallet Addresses Before You Send
Crypto address poisoning plants a lookalike address in your transaction history. Learn how it works and follow a safer verification checklist.

Ethereum Glamsterdam Upgrade: ePBS, BALs, and What Is Actually Planned
Ethereum Glamsterdam is expected in Q4 2026. Learn its frozen scope, Sepolia milestone, ePBS, block-level access lists, and remaining uncertainties.