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
  • Address Members
  • Transfer & Send
  • External Calls
  • Contract Type
  1. LEARN
  2. Introduction
  3. 2. Solidity

2.12 Address Type

It's a type that is specific to Solidity and Ethereum, and it is critical to security: the address type. To highlight its importance, we dedicate a section especially to address.

address refers to the underlying Ethereum account address, the EOA or the contract account. This is different from the addresses that you might have encountered in other programming languages such as C and C++, where they refer to variables' memory address when you're dealing with pointers or references. Here address signifies something very different: an account address.

The address types are 20 bytes in size, because remember that is the size of the Ethereum address. They come in two types they can be plain address types or they can have a payable specifier, and referred to as an address payable type, where it indicates that this address type can receive Ether. There are different operators that operate on address types, such as shown here

  • Operators ==, !=, <, <=, > and >=

  • Implicit/Explicit Conversions

There are conversions that can be performed on address types. Some of them are implicit and others are explicit. For converting address payable types to address types, implicit conversions can be used because it is safe. Whereas the other way around, where an address type is converted to an address payable type, that should be an explicit conversion because now this address becomes capable of receiving Ether.

From a security perspective, address types play a critical role in contracts. These addresses are used in different types of access control: some may be considered as more privileged than the others in the context of the contract logic.

Addresses can also hold Ether balances and token balances, so using the right addresses in the right places and making sure that the correct access control logic or the balances accounting logic is applied on them, becomes very critical from a security perspective to make sure there are no undefined behavior or unintended side effects leading to security vulnerabilities.

Address Members

Address types have different members that can give different aspects of the underlying address types:

  • balance: gives (as the name may suggest) the balance of that address in wei.

  • code: gives (surprise...) the code of that address.

  • code hash: gives the hash of the code.

There are also the transfer and send members that are applicable to the address payable types. In addition, the call, delegate call and static call members can be applied on plain address types. We are going to elaborate on all of these shortly...

So as you can imagine, these address members play a huge role when it comes to the security aspects, because they deal with the balances, look at the code, the code hash, the reentrancy aspects of send and transfer, making external calls using call, delegatecall, and staticcall, which are critical when it comes to the trustworthiness of the contracts that are being called at these addresses.

Transfer & Send

They make calls to the addresses that are specified by supplying a limited gas stipend of only 2300 gas units. This is not adjustable: it is something hard coded in Solidity to address the category of reentrancy attacks on addresses. We'll take a look at the reentrancy aspect later on in the security modules, but it's something to keep in mind for now.

The transfer member is used for transferring Ether to the destination address. This transfer triggers the receive function, or the fallback function of the target contract. From a security perspective this primitive affects reentrancy attacks: where the target contract, if it is untrusted, could potentially call back into the caller contract and lead to undesired behavior that could affect token balances or other contract logic in in a very critical way. The 2300 gas assumption is critical when you look at how contracts use transfer and whether that transaction could fail and revert and lead to undefined behavior.

Similar to transfer, there is send member Solidity's address type, which is somewhat a lower level counterpart for transfer. It is used for Ether transfers: it triggers the same receiver fallback functions like transfer, but it does not result in a failure if the target contract uses more than 2300 gas unlike transfer. In the case of send, it does not revert but it just sends back a boolean return value that indicates a either success (true) or failure (false).

So if the send primitive is used to transfer value, then from a security perspective it means that the return value of that send primitive must be checked by the caller to make sure that the transfer happened successfully or not, depending on what was returned. Again, the send primitive affects reentrancy, which is again why the send primitive was introduced as a mitigation in Solidity. The return value check that is critical and and different from its transfer counterpart.

External Calls

These are used to make low level calls to their specific address that is specified. We talked about some of these calls in the context of the underlying instructions, call, delegatecall, and staticcall instructions, where we talked about how the callee account in the case of delegatecall executes with its logic but with the state of the caller account. Similarly in the case of staticcall we talked about how the callee contract address can access the state but cannot modify the state.

These instructions are used to interface with contracts that do not adhere to the ABI or where the developer wants more direct control over such calls. They all take single bytes memory parameter, return the success condition as a boolean and return data in a bytes memory. They can also use abi.* functions, such as encode, codepath, encode with selector, encoded signature... to encode structured data as part of the arguments.

They can also use gas and value modifiers to specify the amount of gas and Ether for these calls. The latter is applicable for the call primitive but not delegatecall or staticcall.

To summarize: the delegatecall is used where the caller contract wants to use the logic specified by the callee contract but with the state and other aspects of the caller contract itself. So while the code of the given address is used, all other aspects such as storage, balance, message, sender are taken from the current caller contract. The purpose of delegatecall is to enable use cases such as libraries or proxy upgradability, where the logic code is stored in the callee contract but that operates on the state of the caller contract.

staticcall is used where we want the called function in the callee contract to look at or to read the state of the caller contract, but not modify it in any way.

The use of external calls have different types of security implications, these are low level calls that should be avoided in most cases unless absolutely required and there are no alternatives available, because these are calling out to external contracts that may be untrusted, in the context of the current applications threat model or trust model.

These external contracts could result in undefined behavior, use more gas than expected, cause re-entrances to the caller contract and might also return failures where if the return value is not checked, could result in undefined behavior as well.

A counter-intuitive aspect of these low-level calls is that if these calls are made to contract accounts that do not exist for some reason they still return true based on the design of the EVM.

This can have some serious side effects if the contract logic assumes that the external call was successful and executed the logic that it expected it to execute because it got a value of true from these primitives. The mitigation for this aspect of low level calls is to check for contract existence before these calls are made, and have the logic handle it appropriately if they do not exist.

This has resulted in some serious security vulnerabilities being reported in various high-profile smart contract projects, so something to be kept in mind when analyzing the security of smart contracts that use these low level calls.

Contract Type

Every contract that's declared is its own type and these contract types can be explicitly converted to and from address types. That is what they represent underneath. These contract types do not have any operators supported, and the only members of these types are external functions declared in the contract along with any state variables.

Solidity supports some contract related primitives that need to be understood:

  • The keyword this refers to the current contract. Remember: it can be converted to an address type explicitly.

  • The selfdestruct() primitive in Solidity, related to the SELFDESTRUCT instruction in the EVM. This primitive is a high level wrapper on top of that instruction, it takes in a single argument: an address type specifying the recipient.\

    This recipient will receive all the funds in the contract when it is destroyed (the Ether balance in that destroyed contract, when the execution ends). There are some specifics of selfdestruct that need to be kept in mind from a security perspective for several reasons.\

    One of them is that the recipient address specified in this primitive does not execute the receive() function when it is triggered.\

    Recall that contracts can specify a receive function that gets triggered on Ether transfers or under other conditions.\

    In the context of the selfdestruct() primitive, the recipient address happens to be a contract and it specifies a receive() function that does not get triggered when selfdestruct() happens.\

    This is critical because any logic that might be within that receive() function might have been anticipated by the developer to be triggered anytime ether is received by the contract, but selfdestruct() is an exception to that logic.\

    Also the contract gets destroyed by selfdestruct() only at the end of the transaction that has triggered this flow. What this means is that if there is any other logic after selfdestruct() that may revert for various reasons, then that revert undoes the destruction of the contract itself. So just because we see a selfdestruct() in the control flow does not mean that the contract gets destroyed, because logic after that might revert and really not result in the destruction of this contract.

Other primitives specific to the contract type supported by Solidity are beased on the type(x) instruction (where x is a contract type).

The primitives supported are type(C).name that returns a name of the contract, type(C).creationCode and type(C).runtimeCode primitives return the creation and runtime byte codes of that contract.

These are interesting details that are best examined by writing a simple contract and looking at what these primitives return.

There's also the interface id primitive (type(I).interfaceId) that returns the identifier for the interface specified. We'll take a look at the differences between interfaces and contracts later on, but these are primitives that are supported by Solidity specific to the contract or interface type.

Previous2.11 Solidity VariablesNext2.13 Conversions

Last updated 1 year ago

📚
🌀