Solidity Smart Contract Upgradeability


This article is going to focus on Smart Contract upgradability, why this important and how can we achieve it. When dealing with Smart Contracts we need to be able to upgrade our system code. This is because if security critical bugs appear , we should be able to remediate the bugs. We would also want to enhance the code and add more features. Smart Contract upgradability is not as simple as upgrading a normal software due to the blockchain immutability.
As already mentioned by design, smart contracts are immutable. On the other hand, software quality heavily depends on the ability to upgrade and patch source code in order to produce iterative releases. Even though blockchain based software profits significantly from the technology’s immutability, still a certain degree of mutability is needed for bug fixing and potential product improvements.

Preparing for Upgrades   

In order to properly do the upgrade we should be focusing in the following aspects of the project:
  • Have money management strategies in place
  • Create a pause functionality 
  • Have paths to upgrades
    • Switching addresses
    • Switching Oracles
    • Proxy contracts 

The mentioned functionality is mandatory in order to properly maintain and do risk management on your system. The money management strategy has to do with were and how we hold the funds and the system data. The switch address is related to the proxy contract and the rest have to do with the flow paths we designed to upgrade the smart contracts [1]. 

Proxy Contract

The basic idea is using a proxy for upgrades. The first contract is a simple wrapper or "proxy" which users interact with directly and is in charge of forwarding transactions to and from the second contract, which contains the logic. The key concept to understand is that the logic contract can be replaced while the proxy, or the access point is never changed. Both contracts are still immutable in the sense that their code cannot be changed, but the logic contract can simply be swapped by another contract. The wrapper can thus point to a different logic implementation and in doing so, the software is "upgraded"

Note: This abstract proxy contract provides a fallback function that delegates all calls to another contract using the EVM instruction delegatecall. We refer to the second contract as the implementation behind the proxy, and it has to be specified by overriding the virtual _implementation function. Additionally, delegation to the implementation can be triggered manually through the _fallback function, or to a different contract through the _delegate function. 

The most immediate problem that proxies need to solve is how the proxy exposes the entire interface of the logic contract without requiring a one to one mapping of the entire logic contract’s interface. That would be difficult to maintain, prone to errors, and would make the interface itself not upgradeable. Hence, a dynamic forwarding mechanism is required [1].

Proxy Setup

Below we can see that the contract proxy has one to one relationship with all the logic contract proxy. An this is important in order to understand that this setup kind of breaks the immutability of the blockchain.



Ethereum Smart Contract Source Code Review


As Crypto currency technologies are becoming more and more prevalent, as the time is passing by, and banks will soon start adopting them. Ethereum blockchain and other complex blockchain programs are relatively new and highly experimental. Therefore, we should expect constant changes in the security landscape, as new bugs and security risks are discovered, and new best practices are developed [1].This article is going to discuss how to perform a source code review in Ethereum Smart Contracts (SCs) and what to look for. More specifically we are going to focus in specific keywords and how to analyse them. 

The points analysed are going to be:
  • User supplied input filtering, when interacting directly with SC
  • Interfacing with external SCs
  • Interfacing with DApp applications
  • SC formal verification
  • Wallet authentication in DApp

SC Programming Mindset

When designing an SC ecosystem (a group of SCs, constitutes an ecosystem) is it wise to have some specific concepts and security design principles in mind. SC programming requires a different engineering mindset than we may be used to. The cost of failure can be high, and change can be difficult, making it in some ways more similar to hardware programming or financial services programming than web or mobile development. 

When programming SCs we should be able to:
  • Design carefully roll outs
  • Keep contracts simple and modular
  • Be fully aware of blockchain properties
  • Prepare for failure

Key Security Concepts For SCs Systems

More specifically it is mandatory and the responsible thing to  is to take into consideration the following areas:
  • SC ecosystem monitoring e.g. monitor for unusual transactions etc.
  • SC ecosystem governance/admin e.g. by using proxy SC that follow best practices etc.
  • SC formal verification of all the SCs interacting with
  • SC modular/clean coding e.g. use comments and modular code etc. 
  • SC ecosystem code auditing by an external independent 3rd party
  • SC ecosystem system penetration testing by an external independent 3rd party  
Note: At this point we should point out that it is important that DApp smart contract interaction should also be reviewed.

SCs User Input Validation

Make sure you apply user input validation on a DApp and SC level. Remember that on-chain data are public and an adversary can interact with a SC directly, by simply visiting an Ethereum explorer in etherscan.io, the following screenshots demonstrate that.

Below we can see an example of a deployed contract:

Simple by clicking in the SC link we can interact with the contract:

Note:  Above we can see the upgrade function call and various other Admin functions. Of course is not that easy to interact with them.

It is also known that etherscan.io provides some experimental features to decompile the SC code:

 If we click the decompile code we get this screen below:

Below we can see how a DApp or wallet can interact with a SC:

There are five categories of Ethereum wallets that can interact with DApps:
  • Browser built-in (e.g. Opera, Brave etc)
  • Browser extension (e.g. MetaMask )
  • Mobile wallets (e.g. Trust, Walleth, Pillar etc.)
  • Account-based web wallets (e.g. Fortmatic, 3box etc.)
  • Hardware wallets (e.g. Ledger, Trezor etc.)
Then there is a larger category of wallets that cannot integrate with DApps include generic wallet apps that lack the functionality to integrate with smart contracts. Different wallets have a different user experience to connect. For example, with MetaMask you get a Connect pop up. With mobile wallets, you scan a QR code. So phishing attacks take a different for e.g. an attacker can spoof a QR code, through online free QR generators etc. When assessing a DApp the architecture is of paramount importance. A user with a Metamask plugin can use it to connect to the DApp. The DApp automatically will associate the interaction with the user public key to run various tasks. 

For a Web based DApp we can use traditional filtering methods. But for SCs using Solidity we can use assert and require are as convenience functions that check for conditions (e.g. a user supplies input and the mentioned functions check if the conditions are met). In cases when conditions are not met, they throw exceptions, that we help us handle the errors.

These are the cases when Solidity creates assert-type of exceptions when we [3]:
  • Invoke Solidity assert with an argument, showing false.
  • Invoke a zero-initialized variable of an internal function type.
  • Convert a large or negative value to enum.
  • We divide or modulo by zero.
  • We access an array in an index that is too big or negative.
The require Solidity function guarantees validity of conditions that cannot be detected before execution. It checks inputs, contract state variables and return values from calls to external contracts.

In the following cases, Solidity triggers a require-type of exception when [3]:
  • We call require with arguments that result in false.
  • A function called through a message does not end properly.
  • We create a contract with new keyword and the process does not end properly.
  • We target a codeless contract with an external function.
  • We contract gets Ether through a public getter function.
  • We .transfer() ends in failure.
Generally speaking when handling complex user input and run mathematical calculations it is mandatory to use external libraries from 3rd partyaudited code. A code project to look into would be SafeMath from OpenZeppelin. SafeMath is a wrapper over Solidity’s arithmetic operations with added overflow checks. Arithmetic operations in Solidity wrap on overflow. This can easily result in bugs, because programmers usually assume that an overflow raises an error, which is the standard behavior in high level programming languages. SafeMath restores this intuition by reverting the transaction when an operation overflows.

SC External Calls

Calls to untrusted 3rd party Smart Contracts can introduce several security issues. External calls may execute malicious code in that contract or any other contract that it depends upon. As such, every external call should be treated as a potential security risk.  Solidity's call function is a low-level interface for sending a message to an SC. It returns false if the subcall encounters an exception, otherwise it returns true. There is no notion of a legal call, if it compiles, it's valid Solidity.

Especially when the return value of a message call is not checked, execution will resume even if the called contract throws an exception. If the call fails accidentally or an attacker forces the call to fail, this may cause unexpected behavior in the subsequent program logic. Always make sure to handle the possibility that the call will fail by checking the return value of that function [4].

Reentrancy (Recursive Call Attack)

One of the major risks of calling external contracts is that they can take over the control flow. In the reentrancy attack (a.k.a. recursive call attack) calling external contracts can take over the control flow, and make changes to your data that the calling function wasn’t expecting. A reentrancy attack occurs when the attacker drains funds from the target contract by recursively calling the target’s withdraw function [4].

Below we can see a schematic representation:

Note: For more information on this type of attack please see [4]

SC Denial of Service Attacks

Each block has an upper bound on the amount of gas that can be spent, and thus the amount computation that can be done. This is the Block Gas Limit. If the gas spent exceeds this limit, the transaction will fail. 

This leads to a couple possible Denial of Service vectors:
  • Gas Limit DoS on a Contract via Unbounded Operations
  • Gas Limit DoS on the Network via Block Stuffing
Note: For more information on DoS attacks see consensys.github.io

Use Of Delegatecall/Callcode and Libraries

According the Solidity docs [7], there exists a special variant of a message call, named delegatecall which is identical to a message call apart from the fact that the code at the target address is executed in the context of the calling contract and msg.sender and msg.value do not change their values.

This means that a contract can dynamically load code from a different address at runtime. Storage, current address and balance still refer to the calling contract, only the code is taken from the called address. This makes it possible to implement the “library” feature in Solidity: Reusable library code that can be applied to a contract’s storage, e.g. in order to implement a complex data structure.

Improper security use of this function was the cause for the Parity Multisig Wallet hack in 2017. This function is very useful for granting our contract the ability to make calls on other contracts, as if that code were a part of our own contract. However, using delegatecall() causes all public functions from the called contract to be callable by anyone. Because this behavior was not recognized when Parity built its own custom libraries. 


Writing secure smart code is a lot of work and can be very complex.