A developer’s guide to Solidity design patterns
Tags: webdev
Published: 2022-07-21 18:37:07

**Written by [Joel Adewole](https://blog.logrocket.com/author/joeladewole/)✏️**

Due to the continued increasing popularity of blockchain and [DApps](https://blog.logrocket.com/how-build-dapp-host-ipfs-fleek/) (decentralized applications), open source DApps are seeing growth in contributions from a wide variety of developers. The heart of most DApps and blockchain applications are smart contracts developed using Solidity. 

Contribution to open source projects raises concerns within the [Solidity](https://blog.logrocket.com/daml-vs-solidity-choose-smart-contract-language/) community because these projects have real-world consequences for people’s money, and when developers from different backgrounds collaborate on a project, it is almost certain that there will be errors and code conflicts in the applications. This is why practicing proper standards for DApps is so critical. 

To maintain excellent standards, eliminate risks, mitigate conflicts, and construct scalable and secure smart contracts, it is necessary to study and use the correct implementation of design patterns and styles in Solidity. 

This article will discuss the Solidity design pattern; you must be familiar with Solidity to follow along.

### Contents

*   [What is a Solidity design pattern?](#what-solidity-design-pattern)
*   [Behavioral patterns](#behavioral-patterns)
    *   [Guard check](#guard-check)
    *   [State machine](#state-machine)
    *   [Oracle](#oracle)
    *   [Randomness](#randomness)
*   [Security patterns](#security-patterns)
    *   [Access restriction](#access-restriction)
    *   [Check effects interactions](#check-effects-interactions)
    *   [Secure Ether transfer](#secure-ether-transfer)
    *   [Pull-over-push](#pull-over-push)
    *   [Emergency stop](#emergency-stop)
*   [Upgradeability patterns](#upgradeability-patterns)
    *   [Proxy delegate](#proxy-delegate)
    *   [Memory array building](#memory-array-building)
    *   [Eternal storage](#eternal-storage)
*   [Memory vs. storage](#memory-vs-storage)

## What is a Solidity design pattern? <a name="what-solidity-design-pattern">

As a developer, you can learn to use Solidity from various resources online, but these materials are not the same, because there are many different ways and styles of implementing things in Solidity. 

Design patterns are reusable, conventional solutions used to solve reoccurring design flaws. Making a transfer from one address to another is a practical example of frequent concern in Solidity that can be regulated with design patterns. 

When transferring Ether in Solidity, we use the `Send`, `Transfer`, or `Call` methods. These three methods have the same singular goal: to send Ether out of a smart contract. Let's have a look at how to use the `Transfer` and `Call` methods for this purpose. The following code samples demonstrate different implementations. 

First is the `Transfer` method. When using this approach, all receiving smart contracts must define a fallback function, or the transfer transaction will fail. There is a gas limit of 2300 gas available, which is enough to complete the transfer transaction and aids in the prevention of reentry assaults:

```solidity
function Transfer(address payable _to) public payable {     
  _to.transfer(msg.value); 
} 
```

The code snippet above defines the `Transfer` function, which accepts a receiving address as `_to` and uses the `_to.transfer` method to initiate the transfer of Ether specified as `msg.value`. 

Next is the `Call` method. Other functions in the contract can be triggered using this method, and optionally set a gas fee to use when the function executes:

```solidity
function Call(address payable _to) public payable {
    (bool sent) = _to.call.gas(1000){value: msg.value}("");
    require("Sent, Ether not sent");
}
```

The code snippet above defines the `Call` function, which accepts a receiving address as `_to`, sets the transaction status as boolean, and the result returned is provided in the data variable. If `msg.data` is empty, the `receive` function executes immediately after the `Call` method. The fallback runs where there is no implementation of the receive function. 

The most preferred way to transfer Ether between smart contracts is by using the `Call` method. 

In the examples above, we used two different techniques to transfer Ether. You can specify how much gas you want to expend using `Call`, whereas `Transfer` has a fixed amount of gas by default. 

These techniques are patterns practiced in Solidity to implement the recurring occurrence of `Transfer`. 

To keep things in context, the following sections are some of the design patterns that Solidity has regulated.

## Behavioral patterns

### Guard check

Smart contracts' primary function is to ensure the requirements of transactions pass. If any condition fails, the contract reverts to its previous state. Solidity achieves this by employing the EVM's error handling mechanism to throw exceptions and restore the contract to a working state before the exception. 

The smart contract below shows how to implement the guard check pattern using all three techniques:

```solidity
contract Contribution {
  function contribute (address _from) payable public {
    require(msg.value != 0);
    require(_from != address(0));
    unit prevBalance = this.balance;
    unit amount;

    if(_from.balance == 0) {
      amount = msg.value;
    } else if (_from.balance < msg.sender.balance) {
      amount = msg.value / 2;
    } else {
      revert("Insufficent Balance!!!");
    }

    _from.transfer(amount);
    assert(this.balance == prevBalance - amount);
  }
}
```

In the code snippet above, Solidity handles error exceptions using the following: 

`require()` declares the conditions under which a function executes. It accepts a single condition as an argument and throws an exception if the condition evaluates to false, terminating the function's execution without burning any gas. 

`assert()` evaluates the conditions for a function, then throws an exception, reverts the contract to the previous state, and consumes the gas supply if the requirements fail after execution. 

`revert()` throws an exception, returns any gas supplied, and reverts the function call to the contract's original state if the requirement for the function fails. The `revert()` method does not evaluate or require any conditions.

### State machine

The state machine pattern simulates the behavior of a system based on its previous and current inputs. Developers use this approach to break down big problems into simple stages and transitions, which are then used to represent and control an application's execution flow. 

The state machine pattern can also be implemented in smart contracts, as shown in the code snippet below:

```solidity
contract Safe {
    Stages public stage = Stages.AcceptingDeposits;
    uint public creationTime = now;
    mapping (address => uint) balances;

    modifier atStage(Stages _stage) {
      require(stage == _stage);
      _;
    }

    modifier timedTransitions() {
      if (stage == Stages.AcceptingDeposits && now >=
      creationTime + 1 days)
      nextStage();
      if (stage == Stages.FreezingDeposits && now >=
      creationTime + 4 days)
      nextStage();
      _;
    }
    function nextStage() internal {
      stage = Stages(uint(stage) + 1);
    }
    function deposit() public payable timedTransitions atStage(Stages.AcceptingDeposits) {
      balances[msg.sender] += msg.value;
    }
    function withdraw() public timedTransitions atStage(Stages.ReleasingDeposits) {
      uint amount = balances[msg.sender];
      balances[msg.sender] = 0;
      msg.sender.transfer(amount);
    }
}
```

In the code snippet above, the `Safe` contract uses modifiers to update the state of the contract between various stages. The stages determine when deposits and withdrawals can be made. If the current state of the contract is not `AcceptingDeposit`, users can not deposit to the contract, and if the current state is not `ReleasingDeposit`, users can not withdraw from the contract.

### Oracle

Ethereum contracts have their own ecosystem where they communicate. The system can only import external data via a transaction (by passing data to a method), which is a drawback because many contract use cases involve knowledge from sources other than the blockchain (e.g., the stock market). 

One solution to this problem is to use the oracle pattern with a connection to the outside world. When an oracle service and a smart contract communicate asynchronously, the oracle service serves as an API. A transaction begins by invoking a smart contract function, which comprises an instruction to send a request to an oracle. 

Based on the parameters of such a request, the oracle will fetch a result and return it by executing a callback function in the primary contract. Oracle-based contracts are incompatible with the blockchain concept of a decentralized network, because they rely on the honesty of a single organization or group. 

Oracle services [21](https://docs.oracle.com/en/database/oracle/oracle-database/21/whats-new.html) and [22](https://docs.oracle.com/en/cloud/saas/b2b-service/22a/index.html) address this flaw by providing a validity check with the data supplied. Note that an oracle must pay for the callback invocation. Therefore, an oracle charge is paid alongside the Ether required for the callback invocation. 

The code snippet below shows the transaction between an oracle contract and its consumer contract:

```solidity
contract API {
    address trustedAccount = 0x000...; //Account address
    struct Request {
        bytes data;
        function(bytes memory) external callback;
    }
    Request[] requests;
    event NewRequest(uint);

    modifier onlyowner(address account) {
        require(msg.sender == account);
        _;
    }
    function query(bytes data, function(bytes memory) external callback) public {
        requests.push(Request(data, callback));
        NewRequest(requests.length - 1);
    }
    // invoked by outside world
    function reply(uint requestID, bytes response) public
    onlyowner(trustedAccount) {
    requests[requestID].callback(response);
    }
}
```

In the code snippet above, the `API` smart contract sends a query request to a `knownSource` using the `query` function, which executes the `external callback` function and uses the `reply` function to collect response data from the external source.

### Randomness

Despite how tricky it is to generate random and unique values in Solidity, it is in high demand. The block timestamps are a source of randomness in Ethereum, but they are risky because the miner can tamper with them. To prevent this issue, solutions like block-hash PRNG and Oracle RNG were created. 

The following code snippet shows a basic implementation of this pattern using the most recent block hash:

```solidity
// This method is predicatable. Use with care!
function random() internal view returns (uint) {
    return uint(blockhash(block.number - 1));
}
```

The `randomNum()` function above generates a random and unique integer by hashing the block number (`block.number`, which is a variable on the blockchain).

## Security patterns

### Access restriction

Because there are no built-in means to manage execution privileges in Solidity, one common trend is to limit function execution. Execution of functions should only be on certain conditions like timing, the caller or transaction information, and other criteria. 

Here's an example of conditioning a function:

```solidity
contract RestrictPayment {
    uint public date_time = now;

    modifier only(address account) {
        require(msg.sender == account);
        _;
    }

    function f() payable onlyowner(date_time + 1 minutes){
      //code comes here
    }
}
```

The Restrict contract above prevents any `account` different from the `msg.sender` from executing the `payable` function. If the requirements for the `payable` function are not met, `require` is used to throw an exception before the function is executed.

### Check effects interactions

The check effects interaction pattern decreases the risk of malicious contracts attempting to take over control flow following an external call. The contract is likely transferring control flow to an external entity during the Ether transfer procedure. If the external contract is malicious, it has the potential to disrupt the control flow and cause the sender to rebound to an undesirable state. 

To use this pattern, we must be aware of which parts of our function are vulnerable so that we can respond once we find the possible source of vulnerability. 

The following is an example of how to use this pattern:

```solidity
contract CheckedTransactions {
    mapping(address => uint) balances;
    function deposit() public payable {
        balances[msg.sender] = msg.value;
    }

    function withdraw(uint amount) public {
        require(balances[msg.sender] >= amount);
        balances[msg.sender] -= amount;
        msg.sender.transfer(amount);
    }
}
```

In the code snippet above, the `require()` method is used throw an exception if the condition `balances[msg.sender] >= amount` fails. This means, a user can not withdraw an `amount` greater the balance of the `msg.sender`.

### Secure Ether transfer

Although cryptocurrency transfers are not Solidity's primary function, they happen frequently. As we discussed earlier, `Transfer`, `Call`, and `Send` are the three fundamental techniques for transferring Ether in Solidity. It is impossible to decide which method to use unless one is aware of their differences. 

In addition to the two methods(`Transfer` and `Call`) discussed earlier in this article, transmitting Ether in Solidity can be done using the `Send` method. 

`Send` is similar to `Transfer` in that it costs the same amount of gas as the default (2300). Unlike `Transfer`, however, it returns a boolean result indicating whether the `Send` was successful or not. Most Solidity projects no longer use the `Send` method. 

Below is an implementation of the `Send` method:

```solidity
function send(address payable _to) external payable{
    bool sent = _to.send(123);
    require(sent, "send failed");
}
```

The `send` function above, uses the `require()` function to throw an exception if the `Boolean` value of sent returned from `_to.send(123)` is `false`.

### Pull-over-push <a name="pull-over-push">

This design pattern shifts the risk of Ether transfer from the contract to the users. During the Ether transfer, several things can go wrong, causing the transaction to fail. In the pull-over-push pattern, three parties are involved: the entity initiating the transfer (the contract's author), the smart contract, and the receiver. 

This pattern includes mapping, which aids in the tracking of users' outstanding balances. Instead of delivering Ether from the contract to a recipient, the user invokes a function to withdraw their allotted Ether. Any inaccuracy in one of the transfers has no impact on the other transactions. 

The following is an example of pull-over-pull:

```solidity
contract ProfitsWithdrawal {
    mapping(address => uint) profits;
    function allowPull(address owner, uint amount) private {
        profits[owner] += amount;
    }
    function withdrawProfits() public {
        uint amount = profits[msg.sender];
        require(amount != 0);
        require(address(this).balance >= amount);
        profits[msg.sender] = 0;
        msg.sender.transfer(amount);
    }
}
```

In the `ProfitsWithdrawal` contract above, allows users to withdraw the profits mapped to their `address` if the balance of the user is greater than or equal to profits alloted to the user.

### Emergency stop

Audited smart contracts may contain bugs that aren't detected until they're involved in a cyber incident. Errors discovered after the contract launch will be tough to fix. With the help of this design, we can halt a contract by blocking calls to critical functions, preventing attackers until the rectification of the smart contract. 

Only authorized users should be allowed to use the stopping functionality to prevent users from abusing it. A state variable is set from `false` to `true` to determine the termination of the contract. After terminating the contract, you can use the access restriction pattern to ensure that there is no execution of any critical function. 

A function modification that throws an exception if the state variable indicates the initiation of an emergency stop can is used to accomplish this, as show below:

```solidity
contract EmergencyStop {
    bool Running = true;
    address trustedAccount = 0x000...; //Account address
    modifier stillRunning {
        require(Running);
        _;
    }
    modifier NotRunning {
        require(¡Running!);
        _;
    }
    modifier onlyAuthorized(address account) {
        require(msg.sender == account);
        _;
    }
    function stopContract() public onlyAuthorized(trustedAccount) {
        Running = false;
    }
    function resumeContract() public onlyAuthorized(trustedAccount) {
        Running = true;
    }
}
```

The `EmergencyStop` contract above makes use of modifiers to check conditions, and throw exceptions if any of these conditions is met. 

The contract uses the `stopContract()` and `resumeContract()` functions to handle emergency situations. The contract can be resumed by resetting the state variable to `false`. This method should be secured against unauthorized calls the same way the emergency stop function is.

## Upgradeability patterns

### Proxy delegate

This pattern allows upgrading smart contracts without breaking any of their components. A particular message called `Delegatecall` is employed when using this method. It forwards the function call to the delegate without exposing the function signature. 

The fallback function of the proxy contract uses it to initiate the forwarding mechanism for each function call. The only thing `Delegatecall` returns is a boolean value that indicates whether or not the execution was successful. We're more interested in the return value of the function call. Keep in mind that, when upgrading a contract, the storage sequence must not change; only additions are permitted. 

Here's an example of implementing this pattern:

```solidity
contract UpgradeProxy {
    address delegate;
    address owner = msg.sender;
    function upgradeDelegate(address newDelegateAddress) public {
        require(msg.sender == owner);
        delegate = newDelegateAddress;
    }
    function() external payable {
        assembly {
            let _target := sload(0)
            calldatacopy(0x01, 0x01, calldatasize)
            let result := delegatecall(gas, _target, 0x01, calldatasize, 0x01, 0)
            returndatacopy(0x01, 0x01, returndatasize)
            switch result case 0 {revert(0, 0)} default {return (0, returndatasize)}
        }
    }
}
```

In the code snippet above, `UpgradeProxy` handles a mechanism that allows the `delegate` contract to be upgraded once the `owner` executes the contract by calling the fallback function that transfers a copy of the the `delegate` contract data to the new version.

### Memory array building

This method quickly and efficiently aggregates and retrieves data from contract storage. Interacting with a contract's memory is one of the most expensive actions in the EVM. Ensuring the removal of redundancies and storage of only the required data can help minimize cost. 

We can aggregate and read data from contract storage without incurring further expenses using the view function modification. Instead of storing an array in storage, it is recreated in memory each time a search is required. 

A data structure that is easily iterable, such as an array, is used to make data retrieval easier. When handling data having several attributes, we aggregate it using a custom data type such as struct. 

Mapping is also required to keep track of the expected number of data inputs for each aggregate instance. The code below illustrates this pattern:

```solidity
contract Store {
    struct Item {
        string name;
        uint32 price;
        address owner;
    }
    Item[] public items;
    mapping(address => uint) public itemsOwned;
    function getItems(address _owner) public view returns (uint[] memory) {
        uint\[] memory result = new uint[\](itemsOwned[_owner]);
        uint counter = 0;
        for (uint i = 0; i < items.length; i++) {
            if (items[i].owner == _owner) {
                result[counter] = i;
                counter++;
            }
        }
        return result;
    }
}
```

In the `Store` contract above, we use `struct` to design a data structure of items in a list, then we mapped the items to their owners’ `address`. To get the items owned by an address, we use the `getItems` function to aggrgate a memory called `result`.

### Eternal storage

This pattern maintains the memory of an upgraded smart contract. Because the old contract and the new contract are deployed separately on the blockchain, the accumulated storage remains at its old location, where user information, account balances, and references to other valuable information are stored. 

Eternal storage should be as independent as possible to prevent modifications to the data storage by implementing multiple data storage mappings, one for each data type. Converting the abstracted value to a map of sha3 hash serves as a key-value store. 

Because the proposed solution is more sophisticated than conventional value storage, wrappers can reduce complexity and make code legible. In an upgradeable contract that uses eternal storage, wrappers make dealing with unfamiliar syntax and keys with hashes easier. 

The code snippets below shows how to use wrappers to implement eternal storage:

```solidity
function getBalance(address account) public view returns(uint) {
    return eternalStorageAdr.getUint(keccak256("balances", account));
}
function setBalance(address account, uint amount) internal {
    eternalStorageAdr.setUint(keccak256("balances", account), amount);
}
function addBalance(address account, uint amount) internal {
    setBalance(account, getBalance(account) + amount);
}
```

In the code snippet above, we got the balance of an `account` from eternal storage using the `keccak256` hash function in `enternalStorageAdr.getUint()`, and likewise for setting the balance of the account.

## Memory vs. storage

`Storage`, `memory`, or `calldata` are the methods used when declaring the location of a dynamic data type in the form of a variable, but we'll concentrate on `memory` and `storage` for now. The term `storage` refers to a state variable shared across all instances of smart contract, whereas `memory` refers to a temporary storage location for data in each smart contract execution instance. Let's look at an example of code below to see how this works: 

Example using `storage`:

```solidity
contract BudgetPlan {
        struct Expense {
                uint price;
                string item;
        } 
        mapping(address => Expense) public Expenses;
        function purchase() external {
                Expense storage cart = Expenses[msg.sender]
                cart.string = "Strawberry" 
                cart.price = 12
        }
}
```

In the `BudgetPlan` contract above, we designed a data structure for an account’s expenses where each expense (`Expense`) is a struct containing `price` and `item`. We then declared the `purchase` function to add a new `Expense` to `storage`. 

Example using `memory`:

```solidity
contract BudgetPlan {
        struct Expense {
                uint price;
                string item;
        } 
        mapping(address => Expense) public Expenses;
        function purchase() external {
                Expense memory cart = Expenses[msg.sender]
                cart.string = "Strawberry" 
                cart.price = 12
        }
}
```

Almost like the example using `storage`, everything is the same, but in the code snippet we add a new `Expense` to memory when the `purchase` function is executed.

## Closing thoughts

Developers should stick to design patterns because there are different methods to achieve specific objectives or implement certain concepts. 

You will notice a substantial change in your applications if your practice these Solidity design patterns. Your application will be easier to contribute to, cleaner, and more secure. 

I recommend you use at least one of these patterns in your next Solidity project to test your understanding of this topic. 

Feel free to ask any questions related to this topic or leave a comment in the comment section below.

---

## WazirX, Bitso, and Coinsquare use LogRocket to proactively monitor their Web3 apps

Client-side issues that impact users’ ability to activate and transact in your apps can drastically affect your bottom line. If you’re interested in monitoring UX issues, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try [LogRocket](https://logrocket.com/signup/).

[![LogRocket Sign Up](https://blog.logrocket.com/wp-content/uploads/2019/12/cpu-memory-usage.png)](https://logrocket.com/signup/)

[LogRocket](https://logrocket.com/signup/) is like a DVR for web and mobile apps, recording everything that happens in your web app or site. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.

Modernize how you debug web and mobile apps — [Start monitoring for free](https://logrocket.com/signup/).