Secureum Book
  • 🛡️Secureum Bootcamp
    • 🛡️Secureum Bootcamp
    • 🙌Participate
    • 📜History
  • 📚LEARN
    • Introduction
      • 🔷1. Ethereum Basics
        • 1.1 Ethereum: Concept, Infrastructure & Purpose
        • 1.2 Properties of the Ethereum Infrastructure
        • 1.3 Ethereum vs. Bitcoin
        • 1.4 Ethereum Core Components
        • 1.5 Gas Metering: Solving the Halting Problem
        • 1.6 web2 vs. web3: The Paradigm Shift
        • 1.7 Decentralization
        • 1.8 Cryptography, Digital Signature & Keys
        • 1.9 Ethereum State & Account Types
        • 1.10 Transactions: Properties & Components
        • 1.11 Contract Creation
        • 1.12 Transactions, Messages & Blockchain
        • 1.13 EVM (Ethereum Virtual Machine) in Depth
        • 1.14 Transaction Reverts & Data
        • 1.15 Block Explorer
        • 1.16 Mainnet & Testnets
        • 1.17 ERCs & EIPs
        • 1.18 Legal Aspects in web3: Pseudonymity & DAOs
        • 1.19 Security in web3
        • 1.20 web2 Timescales vs. web3 Timescales
        • 1.21 Test-in-Prod. SSLDC vs. Audits
        • Summary: 101 Keypoints
      • 🌀2. Solidity
        • 2.1 Solidity: Influence, Features & Layout
        • 2.2 SPDX & Pragmas
        • 2.3 Imports
        • 2.4 Comments & NatSpec
        • 2.5 Smart Contracts
        • 2.6 State Variables: Definition, Visibility & Mutability
        • 2.7 Data Location
        • 2.8 Functions
        • 2.9 Events
        • 2.10 Solidity Typing
        • 2.11 Solidity Variables
        • 2.12 Address Type
        • 2.13 Conversions
        • 2.14 Keywords & Shorthand Operators
        • 2.15 Solidity Units
        • 2.16 Block & Transaction Properties
        • 2.17 ABI Encoding & Decoding
        • 2.18 Error Handling
        • 2.19 Mathematical & Cryptographic Functions
        • 2.20 Control Structures
        • 2.21 Style & Conventions
        • 2.22 Inheritance
        • 2.23 EVM Storage
        • 2.24 EVM Memory
        • 2.25 Inline Assembly
        • 2.26 Solidity Version Changes
        • 2.27 Security Checks
        • 2.28 OpenZeppelin Libraries
        • 2.29 DAppSys Libraries
        • 2.30 Important Protocols
        • Summary: 201 Keypoints
      • 🔏3. Security Pitfalls & Best Practices
        • 3.1 Solidity Versions
        • 3.2 Access Control
        • 3.3 Modifiers
        • 3.4 Constructor
        • 3.5 Delegatecall
        • 3.6 Reentrancy
        • 3.7 Private Data
        • 3.8 PRNG & Time
        • 3.9 Math & Logic
        • 3.10 Transaction Order Dependence
        • 3.11 ecrecover
        • 3.12 Unexpected Returns
        • 3.13 Ether Accounting
        • 3.14 Transaction Checks
        • 3.15 Delete Mappings
        • 3.16 State Modification
        • 3.17 Shadowing & Pre-declaration
        • 3.18 Gas & Costs
        • 3.19 Events
        • 3.20 Unary Expressions
        • 3.21 Addresses
        • 3.22 Assertions
        • 3.23 Keywords
        • 3.24 Visibility
        • 3.25 Inheritance
        • 3.26 Reference Parameters
        • 3.27 Arbitrary Jumps
        • 3.28 Hash Collisions & Byte Level Issues
        • 3.29 Unicode RTLO
        • 3.30 Variables
        • 3.31 Pointers
        • 3.32 Out-of-range Enum
        • 3.33 Dead Code & Redundant Statements
        • 3.34 Compiler Bugs
        • 3.35 Proxy Pitfalls
        • 3.36 Token Pitfalls
        • 3.37 Special Token Pitfalls
        • 3.38 Guarded Launch Pitfalls
        • 3.39 System Pitfalls
        • 3.40 Access Control Pitfalls
        • 3.41 Testing, Unused & Redundand Code
        • 3.42 Handling Ether
        • 3.43 Application Logic Pitfalls
        • 3.44 Saltzer & Schroeder's Design Principles
        • Summary: 201 Keypoints
      • 🗜️4. Audit Techniques & Tools
        • 4.1 Audit
        • 4.2 Analysis Techniques
        • 4.3 Specification, Documentation & Testing
        • 4.4 False Positives & Negatives
        • 4.5 Security Tools
        • 4.6 Audit Process
        • Summary: 101 Keypoints
      • ☝️5. Audit Findings
        • 5.1 Criticals
        • 5.2 Highs
        • 5.3 Mediums
        • 5.4 Lows
        • 5.5 Informationals
        • Summary: 201 Keypoints
  • 🌱CARE
    • CARE
      • CARE Reports
  • 🚩CTFs
    • A-MAZE-X CTFs
      • Secureum A-MAZE-X
      • Secureum A-MAZE-X Stanford
      • Secureum A-MAZE-X Maison de la Chimie Paris
Powered by GitBook
On this page
  • Parameters
  • Return Variables
  • Modifiers
  • Function Visibility
  • Function Mutability
  • Function Overloading
  • Free Functions
  • Special Functions
  • Constructor
  • Receive Function
  • Fallback Function
  1. LEARN
  2. Introduction
  3. 2. Solidity

2.8 Functions

Functions are the executable units of code. In the case of Solidity, they are usually defined inside a smart contract, but they can also be defined outside of the contracts in which case they are specified at a file level. Such functions are referred to as "free functions".

Functions are what allow modifications to the state that is encapsulated as part of the contract, so they are how logic manifests itself within the smart contracts and the state transitions from one initial state to the modified state, as a result of any of the transactions or messages that interact with the smart contract.

Parameters

Functions typically specify parameters. These are declared just like variables within the function. Parameters are how the caller of the function sends in data into the function for it to work on.

Parameters are used and assigned in a very similar manner to local variables within the function, and the nomenclature that the function specifies the parameter and the caller sends in arguments that get assigned to these parameters in the context of the function.

Return Variables

Functions typically also return values. These are returned using the return keyword. Solidity functions can return single variables or they can return multiple variables. The return variables can also be of 2 types:

  • Named return variables: they have a specific name or names. They are treated just like local variables within the context of the function.

  • Unnamed return variables: an explicit return statement needs to be used to return that variable a return value to the context of the function caller.

The caller specifies arguments that get assigned to the respective parameters of the callee function. The caller function works with these parameters (in the context of that function), does something with them along with all the local variables that might be defined within that function (it can also use the state variables that are declared within that contract) and once it is done with that logic, it can return values back to the caller.

Modifiers

Function modifiers are something unique and specific to Solidity. They are declared using the modifier keyword and the format is something like this

modifier mod() {
    Checks;
    _;
}

As you can see, they are very similar to a function where, because modifiers have some logic encapsulated within them. The underscore acts as a placeholder for the function that we're attempting to modify; because modifiers are used along with functions.

So in this case if there is a function foo() on which this modifier is applied, then whenever this function is called, it goes first to the modifier and depending on any of the checks (any of the logic implemented within that modifier), the function's logic gets called at the point where the underscore is placed within that modifier.

So, if there are a bunch of checks in the modifier prior to the underscore, then those checks implement some preconditions that are evaluated before the function's logic is executed. Similarly, if the underscore precedes the checks in the modifier, the function's logic gets executed first and then the modifier executes its checks.

Examples for the usage here could be access control checks that are implemented as preconditions on the function in the modifier, and they could be post-conditions that could be evaluated if the underscore happens to be before the checks in the modifier, and these could implement some sort of accounting checks in the context of the contract.

Function modifiers play a critical role because they're very often used to implement access control checks, things that allow a contract to specify only certain addresses for example, to call the function where the modifier is applied... This is something that becomes critical when you evaluate the security of smart contracts.

Function Visibility

It is similar to the visibility for state variables functions. Functions have the 4 different visibility specifiers

  • public: these functions are part of the contract interface and they can be called either internally (within the contract) or via messages.

  • external: these functions are also part of the contract interface, which means they can be called from other contracts and via transactions, but they cannot be called internally.

  • internal: these functions on the other hand can only be accessed internally (from within the current contract or contracts deriving from it).

  • private: these functions can be accessed only from within the contract where they are defined and not even from the derived contracts.

Function Mutability

Similar to the state variable mutability, functions also have the concept of mutability. This affects what state can they read or modify. Depending on that there are two function mutability specifiers:

  • view: these functions are allowed only to read the state but not modifying it. This is enforced at the EVM level using the STATICCALL opcode.\

    There are various actions that are considered as state modifying that are not allowed for view functions, these include:

    • Writing to state variables (as should be obvious)

    • Emitting events

    • Creating other contracts

    • Using self-destruct

    • Sending ether to other contracts

    • Calling other functions not marked view or pure

    • Using low level calls

    • Using inline assembly that contain certain opcodes

  • pure: these on the other hand are allowed to neither read contract state nor modify it.\

    The not modification part can be enforced at the EVM level, but the reading part cannot because there are no specific opcodes that allow that. There are various actions that are considered as reading from the state:

    • Reading from state variables (obviously)

    • Accessing the balance of contracts

    • Accessing members of block

    • Transactions or messages

    • Calling other functions not marked as pure

    • Using inline assembly that contain certain opcodes

The read/write mutability aspect of functions again has security implications as you can imagine.

Function Overloading

This is something fundamental to object oriented programming. It means that it supports multiple functions within a contract to have the same name but with different parameters or different parameter types. Overloaded functions are selected by matching the function declarations within the current scope to the arguments supplied in the function call, so depending on the number and the type of arguments the correct function is correctly chosen.

Note that return variables are not considered for the process of resolving overloading, so this notion of overloading is an interesting one that is supported by Solidity given that it is an object-oriented programming language.

Free Functions

They are functions that are defined at the file level (i.e. outside the scope of contracts) and thus these are different from the contract functions (defined within the scope of the contract). Free functions always have implicit internal visibility and their code is included in all the contracts that call them, similar to internal library functions. These functions are not very commonly seen.

Special Functions

Constructor

This concept is specific and unique to Solidity because it applies to smart contracts and the way they are created on Ethereum. Recall contracts on ethereum can be created from outside the blockchain via transactions, or from within the Solidity contracts themselves. When a contract is created, you can imagine that one would want to initialize the contract state in some manner. This is made possible by the constructor.

So the constructor is a special function that gets triggered when a contract is created. A constructor is optional and there can be only one constructor for every contract. These special functions are specified by using the constructor keyword; some of the syntax and semantics have changed over the course of time, but this is how it has been in the most recent versions of Solidity

contract Base {
   uint data;
   constructor(uint _data) public {
      data = _data;   
   }
}

So constructors are used to initialize the state of a contract when they are created and deployed on the blockchain. They're triggered when a contract is created and it's run only once. Once the constructor has finished executing, the final code of the contract is stored on the blockchain and this deployed code does not include the constructor code or any of the internal functions that are called from within the constructor.

From a security perspective, constructors are very interesting because they allow one to examine what initializations are being done to the contact state because if not, the default values of the specific types of state variables are used instead. For example it could be used in the context of the various contract functions, which is an interesting and important aspect when it comes to evaluating the security of smart contracts.

Receive Function

Another special function in the context of Solidity is the receive() function. This function gets triggered automatically whenever there is an Ether transfer made to this contract via send() or transfer() primitives.

It also gets triggered when a transaction targets the contract but with empty CALLDATA. Recall that a transaction that targets a contract specifies which function needs to be called in that contract and what arguments need to be used within the data portion of the transaction, but if that data is empty then the receive function is the function that gets automatically triggered in the contract.

There can only be one receive() for every contract and this function cannot have any arguments, it cannot return anything and it must also have external visibility and a payable state mutability.

payable state mutability is something we haven't discussed so far but what it specifies is that the function that has this payable specifier can receive Ether as part of a transaction and that applies to the receive function as well because it is triggered when Ether transfers happen.

The send and transfer primitives are designed in Solidity to transfer only 2300 gas. The rationale behind this was to prevent the risk, or mitigate the risk of what are known as "reentrancy attacks" which we'll talk more in the security chapter. This minimal amount of gas does not allow a receive() function to do anything much more than some basic logging (using events).

From a security context, the receive() function becomes interesting to evaluate because it affects the Ether balance of a contract and any assumptions in the contract logic that depends on the contract's Ether balance.

Fallback Function

Yet another special function in Solidity. It is very similar to the receive() function with some particularities: the fallback() function gets triggered automatically on a call to the contract if none of the functions in the contract match the function signature specified in the transaction. It also gets triggered if there was no data supplied at all in the transaction and there is no receive() function.

Similar to receive(), there can be only one fallback() for every contract, however this fallback() function can receive and return data if required. The visibility is external and if the fallback() function is meant to receive Ether, then it needs to have the payable modifier specified (similar to the receive() function).

The fallback() function cannot assume that more than 2300 gas can be supplied to it because it can be triggered via the send or transfer primitives and, similar to receive(), the security implications of fallback() have to consider that the Ether balance can be changed via this function, so any assumptions in the contract logic specific to the Ether balance need to be examined.

Previous2.7 Data LocationNext2.9 Events

Last updated 1 year ago

📚
🌀