• YouTube Channel
  • System Status
  • VS Code Extension
  • abi.encodePacked with Dynamic Types

    Overview

    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.