• YouTube Channel
  • System Status
  • VS Code Extension
  • Zero as Parameter

    Overview

    What is the Zero as Parameter vulnerability?

    When calling functions in Solidity, as in most programming languages, arguments may be passed to function parameters as variables or literal values. The literal value 0 may be problematic, as Solidity programs often deal with financial information and addresses; any funds inadvertently sent to the zero address, for example, will be unrecoverable. If program logic dictates that a zero value passed to a parameter is appropriate, Solidity offers named constants which can better express this intent and help avoid inadvertent logical errors.

    Further reading: Solidity Documentation: Constants

    Technical example of vulnerable code

      // SPDX-License-Identifier: Unlicense
      pragma solidity ^0.8.0;
    
      interface IStakingContract {
          function stakeTokens(uint256 amount, uint256 lockPeriod) external;
      }
    
      contract TokenStaking {
          IStakingContract public stakingContract;
    
          address private owner;
    
          mapping(address => bool) private privilegedAccounts;
    
          event TokensStaked(address staker, uint256 amount, uint256 lockPeriod);
    
          constructor(IStakingContract _stakingContract) {
              stakingContract = _stakingContract;
              owner = msg.sender;
          }
    
          function addPrivilegedUser(address user) private {
              require(msg.sender == owner, "Only owner can add privileged users.");
    
              privilegedAccounts[user] = true;
          }
    
          // Function to stake tokens with a specified amount and lock-up period
          function stake(uint256 _amount, uint256 _lockPeriod) external {
              stakingContract.stakeTokens(_amount, _lockPeriod);
              emit TokensStaked(msg.sender, _amount, _lockPeriod);
          }
    
          // Incorrectly implemented function with transposed arguments
          function stakePrivileged(uint256 _amount) external {
              require(privilegedAccounts[msg.sender], "Only privileged accounts can stake with no lockup.");
    
              // Dangerous: Transposing the arguments can lead to logic errors
              // Especially if zero is intended for one but not for the other
              stakingContract.stakeTokens(0, _amount); // Accidental transposition
              emit TokensStaked(msg.sender, 0, _amount);
          }
      }
    
    

    In the example above, contract TokenStaking allows privileged users as specified by the contract owner to stake tokens without a lockup period. The function stakePrivileged() intends to allow this using the interface to the staking contract, passing a literal zero as the the argument for the _lockPeriod parameter. However, the arguments to this function are transposed, which will result in incorrect program behavior. Using a literal zero value makes this transposition potentially easier to miss.

    Technical example of how to fix the vulnerability

      // SPDX-License-Identifier: Unlicense
      pragma solidity ^0.8.0;
    
      interface IStakingContract {
          function stakeTokens(uint256 amount, uint256 lockPeriod) external;
      }
    
      contract TokenStakingUpdated {
          IStakingContract public stakingContract;
    
          address private owner;
    
          mapping(address => bool) private privilegedAccounts;
    
          uint256 constant private PRIVILEGED_STAKING_LOCK_PERIOD = 0;
    
          event TokensStaked(address staker, uint256 amount, uint256 lockPeriod);
    
          constructor(IStakingContract _stakingContract) {
              stakingContract = _stakingContract;
              owner = msg.sender;
          }
    
          function addPrivilegedUser(address user) private {
              require(msg.sender == owner, "Only owner can add privileged users.");
    
              privilegedAccounts[user] = true;
          }
    
          // Function to stake tokens with a specified amount and lock-up period
          function stake(uint256 _amount, uint256 _lockPeriod) external {
              stakingContract.stakeTokens(_amount, _lockPeriod);
              emit TokensStaked(msg.sender, _amount, _lockPeriod);
          }
    
          // Function to allow privileged users to stake with no lockup period
          function stakePrivileged(uint256 _amount) external {
              require(privilegedAccounts[msg.sender], "Only privileged accounts can stake with no lockup.");
    
              stakingContract.stakeTokens(_amount, PRIVILEGED_STAKING_LOCK_PERIOD);
              emit TokensStaked(msg.sender, _amount, PRIVILEGED_STAKING_LOCK_PERIOD);
          }
      }
    
    

    In the revised example above, contract TokenStakingUpdated introduces a constant PRIVILEGED_STAKING_LOCK_PERIOD to express the intent of the stakePrivileged() function. While the named constant in itself will not prevent transposition, it makes it clearer to which parameter it should be passed, and may make debugging easier during testing if a transposition does occur.