Functions

"Writing state" includes: modifying state variables, emitting events, creating contracts, selfdestruct, sending Ether, calling non-view functions, low-level calls, and assembly with state-changing opcodes.

13.1 Anatomy

function name(Type1 param1, Type2 param2)
    <visibility>        // external | public | internal | private
    <mutability>        // pure | view | payable | (none = non-payable, state-changing)
    <modifiers>         // onlyOwner, nonReentrant, ...
    <virtual/override>
    returns (ReturnType1, ReturnType2)
{
    // body
}

13.2 Visibility

Callable from outside only via this.f() (an external call) params can be calldata default for state vars; can take storage pointers still visible on-chain to everyone

13.3 State mutability

May read state May write state May receive Ether no — reverts if msg.value > 0

"Writing state" includes: modifying state variables, emitting events, creating contracts, selfdestruct, sending Ether, calling non-view functions, low-level calls, and assembly with state-changing opcodes. view functions are executed with STATICCALL when called externally, so state changes are impossible.

13.4 Parameters and return values

External functions cannot return mappings; internal functions can return storage pointers.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract Returns {
    // Unnamed return
    function add(uint256 a, uint256 b) public pure returns (uint256) {
        return a + b;
    }

    // Named returns: implicitly returned at the end
    function divmod(uint256 a, uint256 b) public pure returns (uint256 q, uint256 r) {
        q = a / b;
        r = a % b;
    }

    // Multiple returns, destructuring
    function use() external pure returns (uint256) {
        (uint256 q, ) = divmod(17, 5);
        return add({a: q, b: 1});           // named arguments in calls
    }

    // Returning dynamic data
    function names() external pure returns (string[] memory list) {
        list = new string[](2);
        list[0] = "alice";
        list[1] = "bob";
    }
}

13.5 Overloading

Functions with the same name but different parameter types are allowed (return types alone don't count):

Calling f(1) is ambiguous if both f(uint8) and f(uint256) exist — literals fit multiple types. Overloads appear as separate entries in the ABI (ethers.js then requires the full signature: contract["f(uint256)"](1)).

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract Overload {
    function f(uint256 x) public pure returns (uint256) { return x; }
    function f(uint256 x, uint256 y) public pure returns (uint256) { return x + y; }
    function f(address a) public pure returns (address) { return a; }
}

13.6 Function types

Functions are first-class values with types like function (uint256) external returns (bool):

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

library ArrayUtils {
    function map(uint256[] memory xs, function (uint256) pure returns (uint256) f)
        internal pure returns (uint256[] memory r)
    {
        r = new uint256[](xs.length);
        for (uint256 i; i < xs.length; ++i) r[i] = f(xs[i]);
    }

    function reduce(uint256[] memory xs, function (uint256, uint256) pure returns (uint256) f)
        internal pure returns (uint256 acc)
    {
        for (uint256 i; i < xs.length; ++i) acc = f(acc, xs[i]);
    }
}

contract FunctionTypes {
    using ArrayUtils for uint256[];

    function square(uint256 x) internal pure returns (uint256) { return x * x; }
    function sum(uint256 a, uint256 b) internal pure returns (uint256) { return a + b; }

    function sumOfSquares(uint256[] memory xs) external pure returns (uint256) {
        return xs.map(square).reduce(sum);
    }

    // External function values carry an address + selector
    function selectorOf() external view returns (bytes4, address) {
        return (this.selectorOf.selector, this.selectorOf.address);
    }
}

13.7 Free functions

Functions defined outside contracts are always implicitly internal; their code is included in every contract that calls them. They cannot access this or state variables directly.

13.8 The getter / this gotcha

Calling this.f() performs a real external call (new call frame, more gas, msg.sender becomes the contract itself). Call internal/public functions directly by name instead.