drawer logo

Why To Optimize Smart Contract Code For Blockchain Fee Reduction

We’ve all encountered desktop and mobile applications whose developers needlessly drain system resources. But with smart contracts, such recklessness comes at an actual price: more fees. But optimization for total cost of ownership is possible. Blockchain Journal explains

Total Cost of Ownership

Smart Contracts

DLT Strategy

By Bob Reselman

Published:January 4, 2023

clock icon

12 min read

In this Story
Amazon Web ServicesAmazon Web Services

Smart contracts are a transformational part of the Distributed Ledger Technology (DLT) landscape. Smart contracts, which are programs that are hosted and run on some public distributed ledgers, transform those ledgers from simple mechanisms for storing and moving cryptocurrencies to industrial-strength application platforms that make the unique capabilities of blockchain programmatically available to business applications. Yes, they bring added power. But that power also comes at a price that can increase a blockchain initiative's total cost of ownership (TCO) for those organizations that incorporate smart contracts into their business operations.

In this article, we'll describe briefly what a smart contract is, the costs they incur at a conceptual level, and how these costs can be optimized to reduce the TCO of a smart contract-powered blockchain project. Of course, there's more to the TCO of blockchain projects than just the smart contracts that are associated with them. For more detail on how blockchain fees work, be sure to read Blockchain Journal's CTO's Guide to the Basics of Blockchain Fees.

The intended readers of this article are business professionals who have a limited understanding of computer programming. The goal is not only to provide a sense of the work that's required to optimize a smart contract but to set the stage for a productive conversation with an organization's candidate smart contract developers. Still, the high-level concepts presented in the following sections might be useful to professional software developers who are new to programming smart contracts.

Smart Contracts Make the Blockchain Programmable

As mentioned above, smart contracts add a new dimension to using blockchain and DLT. Smart contracts have done for DLT what JavaScript did for web pages.

Before JavaScript came along, web pages did nothing more than statically render text and images. Even doing something as simple as creating a web page that allowed a user to add two numbers together was impossible. Today, doing so is a trivial piece of JavaScript programming. It's small potatoes, something an elementary school student can do. But JavaScript goes way beyond the trivial. Look under the covers of every modern web page, from Amazon to Facebook, and you'll find a layer of complex JavaScript powering most of the web's dynamic interactivity. Even the user interface of BlockchainJournal.com, which will become increasingly interactive overtime, is built with sophisticated JavaScript frameworks such as React and Gatsby.

Just as JavaScript changed the nature and scope of developing on the web, so too are smart contracts that change the way people use DLTs. For example, the retail giant Walmart uses DLT-enabled smart contracts to execute supply chain tracking of goods from farms and assembly lines throughout the world to the stores in which the end products are eventually sold. Insurance companies such as Insurwave use smart contract technology to capture and execute the business logic for its policy administration processes. These are just a few examples.

Smart contracts are powerful no doubt, but this power comes with a price. Smart contracts cost money to run.

There's No Free Lunch When it Comes to Running a Smart Contract

With most public distributed ledgers, every time a smart contract executes, it incurs fees that are paid in the cryptocurrency that is native to the ledger on which the smart contract runs. The fees can be minimal or exorbitant. The overall cost typically depends on two things: the scope of activity the smart contract performs and the way the smart contract itself is structured.

In terms of scope of activity, the cost can be minimal when a smart contract doesn't need to do anything more than a single coin exchange from a sender's account to a receiver's account. The fee will be incurred according to the computing resources the smart contract uses to run the single exchange in code as well as the actual exchange fee charged by the DLT to do the transfer.

Poorly developed smart contracts can have a lot of costly fat. Some can be quite lean.

On the other hand, should a smart contract execute several transactions between a number of accounts, according to complex business logic, the overall fee will be much higher. However, at least to some extent, the degree to which extra expense is necessary is manageable. Smart contract fees can be minimized so the smart contract is incurring only the expenses that are essential in order to do the necessary work. Poorly developed smart contracts can have a lot of costly fat. Some can be quite lean. It all depends on the extent to which the smart contract has been optimized.

Based on Blockchain Journal's research into a variety of smart contract-enabled chains, there are typically two ways to minimize the expense of running a smart contract. One way to minimize the fee is to write logic in the smart contract that determines the best time and way to execute the contract's transactions. (Transaction fees vary according to when and how the given transaction is executed. For more about the details of transaction fees read Understanding the Details of Bitcoin Transaction Fees and Understanding the Details of Ethereum Transaction Fees.)

Similar to the way that software developers write source code in a programming language such as Java to create applications that automate a business process, languages such as Solidity are used by developers to write source code for smart contracts. As such, the second way to minimize the cost of running a smart contract is to optimize the structure of the smart contract source code itself.

The thing to understand about running smart contacts is that fees are typically incurred every time computing resources are used. A smart contract will be charged according to the amount of CPU, memory, network I/O, and storage it uses. It's similar to how cloud services such as AWS, Google Cloud, and Azure make money. The more you use, the more you're charged. Thus, smart contract developers need to be keenly aware of the actual computing burden their smart contracts make on the underlying DLT and optimize their source code accordingly.

Optimizing the cost of running a smart contract depends on the programming language used and the DLT upon which the smart contract runs. But, in general, cost optimization comes down to two factors: the way a smart contract uses data types and the structure of its functions.

Let's look at the details using an example that relies on the Solidity programming language. Within the blockchain industry, Solidity is emerging as the de facto standard programming language for creating smart contracts. Solidity is supported by a variety of distributed ledgers that enable smart contracts through the support of an Ethereum Virtual Machine (EVM).

The place to start is with optimization in terms of data types.

Optimizing the Cost of Variables

A variable is an instance of a data type. A data type is a type of value. For example, the data type of the value 1001 is an integer. The data type of the values true or false is a boolean. The value Mick Jagger is a string. These are but a few. There are others that are defined according to the programming language.

The following is an example of declaring a variable named my_age as an integer in the Solidity smart contract programming language:

int my_age;

In the expression shown above, the term int indicates that the variable named my_age is of data type integer.

Under Solidity, there are other data types for integers; int is akin to a general declaration. For example, there are specific integer types such as int8, int16, and int32, to name a few. Each of these represents a data type for integers that have a limited value. For example, a variable of uint8 has a maximum positive value of 255, while a variable of uint16 can hold a maximum positive value of 65535. (The number in the integer type name indicates how many bits the data type has. For example, uint8 has 8 bits, which translates into 1 byte. uint16 has 16 bits which translate into 2 bytes.)

When programming in Solidity, the thing to understand about data types is that the gas fee charged to the smart contract will vary according to the data type and the order in which variables are declared. Consider the following variables that are declared within a Solidity struct that we have conveniently named Notpacked. (struct is a Solidity keyword for structure. A structure is a programming entity for organizing a collection of variables under a name.)

1struct Notpacked {
2 uint64 a;
3 uint256 b;
4 uint64 c;

The expression above declares three variables: a, b, and c. Notice that variables a and c are of data type uint64, but that variable b is of uint256. This makes a difference because of the way that Solidity manages storage when running a smart contract. The Solidity compiler allocates storage in 32-byte groups, with each 32-byte group incurring a distinct fee. Also, Solidity allocates storage according to the order in which variables are declared. Thus, in a situation in which the Solidity compiler encounters four variables of type uint64 in order (each uint64 variable is allocated 8 bytes of storage), all four variables will be allocated to a single 32-byte block of storage. The code will be charged a fee only for the single 32-byte block.

Unfortunately, the Notpacked structure has not been optimized. Notpacked will be allocated three 32-byte blocks of storage because the first variable uint64 a and the second variable uint256 b add up to 40 bytes, which exceeds the 32-byte block size storage limit. Therefore, the variable uint64 a will be assigned to a 32-byte block of storage, the variable uint256 b will be assigned to a 32-byte block, and uint64 c will be assigned to a third 32-byte storage block.

This is considered an unpacked declaration because the structure has not been optimized in terms of storage allocation.

Now, look a the following declaration for a struct named Packed:

1struct Packed {
2    uint64  a;
3    uint64  c;
4    uint256 b;

Notice that variables a and c are grouped together in sequence. Since they are declared in contiguous order, the pair of variables uint64 a and uint64 c can "fit" in a single 32-byte storage block. Also, notice that variable b of data type uint256 comes after the first two and will require a single 32-byte storage block. Thus, the structure named Packed will require two 32-byte storage blocks and will only be charged a fee for those two storage blocks. This is called packing. The previous example using the Unpacked structure required three 32-byte storage blocks. However, the Packed structure contains the same variables as its unoptimized counterpart, but requires only two 32-byte storage blocks, which in turn will result in significant savings in terms of fees associated with that particular structure.

The packed versus unpacked concern is one of many issues related to optimizing smart contract code. There are also similar issues about optimizing smart contracts in terms of functions. For example, even the way a function is declared can affect how smart contract fees are charged. Let's look at this problem, again using a Solidity programming example.

Functions Cost Money

Much work needs to be done in computer programming to turn text-based code into a format that can be understood by a computer. This work is performed by a tool called a compiler which is available for most programming languages. This is as true today as it was over a half-century ago during the early days of mainframe programming.

Solidity charges a fee each time the compiler needs to do a lookup to find a function in code. The order of functions in a lookup is determined by the compiler.

Examine the list of functions below. The list orders the functions by name (for function names, we used colors to keep it simple) along with their underlying equivalent identifiers in hexadecimal notation in the compiled lookup table. (You can think of the hexadecimal equivalent as the way a function is uniquely identified by the computer.)

2red()    => 2930cf24
3white()  => a0811074
4yellow() => be9faf13
5blue()   => ed18f0a7
6purple() => ed44cd44
7green()  => f2f1e132

This list is structured according to the way that the Solidity compiler orders functions for lookup at runtime. The Solidity compiler orders functions according to the hexadecimal equivalent function identifiers in ascending order. For example, the red() function has a hexadecimal equivalent of 2930cf24 and white() as a hexadecimal equivalent of a0811074. Since the hex value 2930cf24 is less than a0811074, the function red() comes before white().

Notice that the function red() is at the top of the list and the function green() is at the bottom.

The way that Solidity works is that it charges a transaction fee that's calculated according to every level it descends through the list to find a function. Thus, using the function green() at the bottom of the list is going to cost more than using the function red() because green() is further down the list, which requires more computing labor to do the lookup. More computing labor translates into a greater cost to run the code. As a result, attention needs to be given to code optimization in order to minimize the cost of executing the code for a given smart contract.

Comprehensively detailing the methods for optimizing functions in Solidity is a subject beyond the scope of this article, one that is worthy of a Blockchain Journal rabbit hole all to itself. However, executives who intend to incorporate smart contracts in their enterprise's operations need to be aware that programming smart contracts involve computer programming skills that sometimes go down to the compiler level. This type of programming requires a special kind of talent, and given the rising popularity of blockchain as an application platform, that talent is difficult to find.

The Takeaway

Computing has become so cheap over the last few decades that most developers don't concern themselves with the minutiae of low-level computing that's required to optimize smart contract code. They primarily focus on implementing the business logic that drives their applications. They write high-level code and let sophisticated tools take care of the low-level details and move on to the next project.

However, the world of programming smart contracts is much different. Similar to the way serverless offerings such as AWS Lambda and Google Cloud Functions work when it comes to smart contracts, every CPU cycle counts. Something as subtle as the order of a function lookup in compiled code can significantly impact the cost of executing a smart contract, particularly one that executes hundreds or thousands of times a day.

As with variable declaration, the important thing to understand in terms of functions is that transaction fees are going to be incurred for every bit of computing resource the smart contract uses. Details count. They really do! And the people who create this kind of code need to be well-versed in the intricacies of smart contract discipline.

The takeaway is that smart contracts cost money to run, but those costs can be reduced when attention is paid to the details that offer opportunities for optimization. The scope of such details is twofold. First, there is the actual logic that the smart contract executes; the second is the structure of the smart contract code itself. Optimizing both will require expertise and experience that may take time to acquire.

footer logo

© 2024 Blockchain Journal