In the process of developing smart contract, because we want to be able to achieve matching function, we have implemented a function that can input multiple orders. As more and more features are added to our function and more and more arguments are required to ensure contractual fairness, such as miners’ fees, taker’s fee, maker’s fee and payment method of fee. We encounter to the variable too much and can not compile, and a lot of data consumed gas has also become a problem.

In our matching function, we will have this information for each order:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
uint256 amountSell,
uint256 amountBuy,
address tokenSell,
address tokenBuy,
address user,
uint256 nonce,
uint256 gasFee,
uint256 takerFee,
uint256 makerFee,
uint256 joyPrice,
bool isBuy,
uint8 v,
byte32 r,
byte32 s

So we tried to find ways to reduce the number of variables. One day I looked at a transaction in Etherscan which shows the following information:

1
2
3
4
5
6
7
8
9
10
11
12
13
Function: trade(address tokenGet, uint256 amountGet, address tokenGive, uint256 amountGive, uint256 expires, uint256 nonce, address user, uint8 v, bytes32 r, bytes32 s, uint256 amount) ***
MethodID: 0x0a19b14a
[0]:0000000000000000000000000000000000000000000000000000000000000000
[1]:000000000000000000000000000000000000000000000000006a94d74f430000
[2]:000000000000000000000000a92f038e486768447291ec7277fff094421cbe1c
[3]:0000000000000000000000000000000000000000000000000000000005f5e100
[4]:000000000000000000000000000000000000000000000000000000000024cd39
[5]:00000000000000000000000000000000000000000000000000000000e053cefa
[6]:000000000000000000000000a11654ff00ed063c77ae35be6c1a95b91ad9586e
[7]:000000000000000000000000000000000000000000000000000000000000001c
[8]:caa3a70dd8ab2ea89736d7c12c6a8508f59b68590016ed99b40af0bcc2de8dee
[9]:26e2347abfba108444811ae5e6ead79c7bd0434cf680aa3102596f1ab855c571
[10]:000000000000000000000000000000000000000000000000000221b262dd8000

I found that all arguments are 256 bits, regardless of the type is byte32, address or uint8. So most of the arguments on the left have a large number of “0” is unused bits. It is easy to think of using these “spaces”.

Data Merging

First, uin256 and byte32 will fill the entire variable space, so the amount and signature r, s can not be used. I chose the address variable to use. In addition, We made adjustments that do not require too many bits of type:

1
2
3
4
5
nonce -> 40 bits
takerFee -> 16 bits
makerFee -> 16 bits
uint256 joyPrice -> 28 bits
isBuy -> 4 bits (In fact, 1 bit is enough. Just for the easy presentation of documents)

A address variable, for example:

1
000000000000000000000000a92f038e486768447291ec7277fff094421cbe1c

It can be stuffed into the above information into:
Solidity Data Compression

The above information corresponds to the following:

1
2
3
4
5
nonce: 0181bfeb
takerFee: 0014
makerFee: 000a
joyPrice: 0000000
isBuy: 1

The original arguments can be reduced to:

1
2
3
4
5
6
7
8
9
uint256 amountSell,
uint256 amountBuy,
uint256 tokenSellWithData,
address tokenBuy,
address user,
uint256 gasFee,
uint8 v,
byte32 r,
byte32 s

Of course, v can also be stuffed into another address variables. I will not repeat them.

Numbering

This has saved a lot of arguments, but I have a new idea: token address should be limited, it is better to use the automatically increment to number just like database? So we further number token and user. The final data becomes:
Solidity Data Compression

The three rightmost ones represent tokenSell, tokenBuy, and user. The orange one indicates the signature v. In the end, our order information is compress to:

1
2
3
4
5
6
uint256 amountSell,
uint256 amountBuy,
uint256 data,
uint256 gasFee,
byte32 r,
byte32 s

Save from the original 14 variables to 6 variables.

Further Reading

Previous Post: Solidity Gas Optimizations - Function Name
Next Post: Solidity Gas Optimizations - Variable Ordering