Skip to content

Instantly share code, notes, and snippets.

@niran
Created September 24, 2017 00:07
Show Gist options
  • Save niran/7666a87844a4438460ad19a52383c3de to your computer and use it in GitHub Desktop.
Save niran/7666a87844a4438460ad19a52383c3de to your computer and use it in GitHub Desktop.

State Specifiers and STATICCALL

Solidity 0.4.16 adds two new specifiers on contract functions: pure and view. Solidity 0.4.17 now enforces those specifiers—if you attempt to read or write to storage in the body of a function that claims not to do so, your contract will not compile.

  • Pure has been introduced to specify functions that do not read or modify the storage of any contract, like math functions.
  • View has been introduced to indicate functions that can never change the storage of any contract. View is an alias for constant, an existing specifier that was also not enforced by the compiler.

Use these specifiers to clarify the intended behavior of your functions. As you write your contracts, Solidity will suggest one of these state mutability specifiers when it detects functions that don't read and/or write to the contract's storage.

Today, pure and view only affect the JSON ABI that the compiler produces. The stateMutability field for a function indicates whether the function is pure, view, nonpayable, or payable. Tools can access this data without parsing the contracts themselves to aid in generating reports or visualizations that make it easier to understand contracts.

The Byzantium network upgrade scheduled for October 9 will add a STATICCALL opcode that enforces read-only calls at runtime. Calls to and within pure or view functions can be compiled as STATICCALL, ensuring that the developer's expectations of immutability are never violated.

STATICCALL allows a subset of reentrancy vulnerabilities to be avoided: if a contract's state change depends on reading data from another contract, it can safely retrieve it without ever triggering a conflicting state change. However, if your contract's state change requires a successful state change in another contract, STATICCALL cannot be used, so you still need to take precautions against reentrancy.

Examples

simple-token-sale

The Disbursement contract in simple-token-sale allows tokens to vest gradually during a specified disbursement period. Disbursement.calcMaxWithdraw() calculates the number of tokens that can currently be withdrawn. In the current codebase the function is specified as constant. In terms of the new specifiers, it is a view function---it reads from a contract's state but it doesn't modify it.

If we remove the existing constant modifier in the codebase, Solidity will detect that the function is read-only:

Disbursement.sol:95:5: Warning: Function state mutability can be restricted to view
    function calcMaxWithdraw()
    ^

Adding the view modifier as shown below makes it clear that the function only reads from the state, and allows the compiler to help us maintain that behavior through the lifetime of the codebase.

/// @dev Calculates the maximum amount of vested tokens
/// @return Number of vested tokens to withdraw
function calcMaxWithdraw()
    public
    view
    returns (uint)
{
    uint maxTokens = (token.balanceOf(this) + withdrawnTokens) * (now - startDate) / disbursementPeriod;
    if (withdrawnTokens >= maxTokens || startDate > now)
        return 0;
    return maxTokens - withdrawnTokens;
}

If we later modify the function and violate that specifier (intentionally or not), Solidity will warn us:

Disbursement.sol:102:9: Warning: Function declared as view, but this expression (potentially) modifies the state and thus requires non-payable (the default) or payable.
    withdrawnTokens = 0;
    ^-------------^

To avoid overlooking this kind of behavior change, we can add pragma experimental "v0.5.0" to the file to make the compilation fail with an error instead of just logging a warning.

zeppelin-solidity

The SafeMath contract in zeppelin-solidity provides math functions that throw exceptions to prevent integer overflows. It's a library contract without its own state. In the current codebase, these functions are specified as constant, which indicates that the function doesn't write to the state, but doesn't let you know if it reads from the state. In terms of the new specifiers, these are pure functions---they don't read or write any state.

Compiling SafeMath with the latest Solidity will throw several warnings like this one to let us know that the functions aren't just constant or view, they're pure:

SafeMath.sol:27:3: Warning: Function state mutability can be restricted to pure
  function add(uint256 a, uint256 b) internal constant returns (uint256) {
  ^

Updating these modifiers to pure communicates the behavior more precisely:

function add(uint256 a, uint256 b) internal pure returns (uint256) {
  uint256 c = a + b;
  assert(c >= a);
  return c;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment