abi.encodePacked with Dynamic Types
Overview
- Severity: High
- Confidence: High
- Affected Versions: All
What is the abi.encodePacked with Dynamic Types vulnerability?
The Solidity language offers a built-in capability for performing ABI-encoding and decoding of data. The function abi.encodePacked()
will pack its argument data tightly, which means that it is possible for different sets of arguments to resolve to the same packed data. If the arguments are meant to be used as a way of selecting unique data, this may lead to collisions.
Further reading: Solidity Documentation: ABI Encoding and Decoding Functions
Technical example of vulnerable code
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
contract SimpleVoting {
// Mapping from session ID to votes (for simplicity, boolean votes)
mapping(bytes32 => bool[]) public sessionVotes;
// Event for creating a session
event SessionCreated(bytes32 sessionId);
// Event for a vote being cast
event VoteCast(bytes32 sessionId, bool vote);
// Create a voting session with a unique identifier based on its details
function createSession(string memory name, string memory date) public {
bytes32 sessionId = keccak256(abi.encodePacked(name, date));
emit SessionCreated(sessionId);
}
// Cast a vote in a specific session
function castVote(bytes32 sessionId, bool vote) public {
sessionVotes[sessionId].push(vote);
emit VoteCast(sessionId, vote);
}
// Other voting logic omitted for brevity
}
In the example above, contract SimpleVoting
tracks votes in different "sessions" (which could be topics related to the management of a Decentralized Autonomous Organization, for instance), using the name and date of the voting session as a unique identifier. However, both of these are passed as string arguments to abi.encodePacked()
in function createSession()
(strings are dynamic types in Solidity), meaning there could be argument collisions: as an example, session name "Vote1" with date "21524" (intended to represent Feburary 15, 2024) could collide with "Vote12" on date "1524" (intended to represent January 5, 2024).
While this example is contrived for the purposes of illustration, it is possible that less obvious and more harmful collisions might occur in actual smart contracts.
Technical example of how to fix the vulnerability
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
contract SimpleVotingUpdated {
// Mapping from session ID to votes (for simplicity, boolean votes)
mapping(bytes32 => bool[]) public sessionVotes;
// Event for creating a session
event SessionCreated(bytes32 sessionId);
// Event for a vote being cast
event VoteCast(bytes32 sessionId, bool vote);
// Create a voting session with a unique identifier based on its details
function createSession(string memory name, string memory date) public {
bytes32 sessionId = keccak256(abi.encode(name, date));
emit SessionCreated(sessionId);
}
// Cast a vote in a specific session
function castVote(bytes32 sessionId, bool vote) public {
sessionVotes[sessionId].push(vote);
emit VoteCast(sessionId, vote);
}
// Other voting logic omitted for brevity
}
In the revised example above, contract SimpleVotingUpdated
now uses abi.encode()
rather than abi.encodePacked()
in function createSession()
, which will consume more memory but will avoid potential collisions related to argument packing in the previous example. An alternative fix would be changing the arguments passed to abi.encodePacked()
to use no more than one dynamic argument to create the unique session identifier.