What's the best way to optimize the bytecode size of a contract?

Hi, I’m currently working on a POC for an hackathon and stumbled upon the 24K bytecode limit in solidity due to the https://github.com/ethereum/EIPs/blob/master/EIPS/eip-170.md .
What’s the best way to reduce the bytecode size?

My contract is currently about 45K.
I’m using

optimizer: {
   enabled: true,
   runs: 1
} 

in truffle-config.js and I already moved part of the code in a separate library.

I’m using some contracts from openzeppelin-solidity

import "openzeppelin-solidity/contracts/token/ERC777/ERC777.sol";
import "openzeppelin-solidity/contracts/utils/ReentrancyGuard.sol";

import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol";

but maybe it’s better to reuse some of them through openzeppelin-eth? Would it save substantial amount of gas?

Thank you

4 Likes

Hi @bugduino

Reading on stackexchange, refactoring into libraries appears to be your best option.

Based on the Solidity documentation your optimizer settings look correct.

https://solidity.readthedocs.io/en/latest/using-the-compiler.html?highlight=optimizer
If you want the initial contract deployment to be cheaper and the later function executions to be more expensive, set it to --optimize-runs=1

Out of the contracts that you are using from openzeppelin-solidity, ERC777 is the largest in terms of bytecode size, but it isn't part of openzeppelin-eth currently.

I created a Sample contract to import your dependencies to get their sizes.

pragma solidity ^0.5.0;

import "openzeppelin-solidity/contracts/token/ERC777/ERC777.sol";
import "openzeppelin-solidity/contracts/utils/ReentrancyGuard.sol";

import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol";

contract Sample {
}

I calculated sizes as per ganache/issues/960#issuecomment-437768131

$ grep \"bytecode\" build/contracts/* | awk '{print $1 " " length($3)/2}'
build/contracts/Address.json: 113.5
build/contracts/ERC777.json: 12627.5
build/contracts/IERC1820Registry.json: 2.5
build/contracts/IERC20.json: 2.5
build/contracts/IERC777.json: 2.5
build/contracts/IERC777Recipient.json: 2.5
build/contracts/IERC777Sender.json: 2.5
build/contracts/Migrations.json: 784.5
build/contracts/ReentrancyGuard.json: 2.5
build/contracts/SafeERC20.json: 113.5
build/contracts/SafeMath.json: 113.5
build/contracts/Sample.json: 84.5

Hopefully someone in the community can assist with this.

2 Likes

Hey there @bugduino!

Could you share some more details about your project? Often large contracts can be broken down into smaller parts that can call into each other, but that’s on a very case-by-case basis.

3 Likes

Hey @abcoathup! I tried calculating sizes with the method described in the ganache issue but they not match up with the sizes I was looking at. Usually I’m taking the deployedBytecode value of the main contract and put into a new file (eg bytecode.hex), then making
ls -l bytecode.hex which currently gives me 45417.
With the other method I got 22709.5 which seems fine but when I try to deploy I got the out-of-gas error.

2 Likes

Hi @nventuro! I’ll share you the repo in a couple of hours

2 Likes

@abcoathup @nventuro here are the contracts https://github.com/bugduino/idle/tree/master/contracts (they are still a WIP and a bit raw, tests should be implemented as soon as I’m able to get the size down to 24K).
The main one is IdleDAI. The IdleHelp is the library I made so far. I’m still not very familiar with writing libraries, for example how can I move the claimITokens method (which calls the rebalance method of the main contract) in the library?

2 Likes

Not sure about this but worth checking: have you tried without the optimizer? I think it optimizes for gas usage so not necessarily for contract size, though maybe the runs: 1 parameter does bias the compiler to optimize for size.

2 Likes

Are you counting characters in bytecode.hex? That would give you 2x the number of bytes, since one byte is represented with two hexadecimal digits.

If you're getting an out of gas error it could be because the constructor is reverting?

2 Likes

I tried without the optimizer but got the same result. I have like 4 small tests for the contstructor which are passing.
Isn’t ls -l or ls -lh already returning the size in KB?

2 Likes

The constructor is very simple anyway

constructor(address _cToken, address _iToken, address _token)
    public
    ERC777("IdleDAI", "IDLEDAI", new address[](0)) {
      owner = msg.sender;
      cToken = _cToken;
      iToken = _iToken;
      token = _token;
      blocksInAYear = 2102400; // ~15 sec per block
      minRateDifference = 500000000000000000; // 0.5% min
  }
2 Likes

If i may suggest, i have run into this issue before myself. One solution that I like, and plays nice with ZeppelinOS is to group each piece of functionality of the contract together and then deploy this chunk of associated code as an EVM package. This gives the added benefit of being able to optionally upgrade the logic of different parts of your application separately.

This way you can avoid the contract size problem all together, create small, easily testable, isolated segments of code functionality. The end result is easier to test, easier to reason about and easier to fix if a problem occurs. It is my preferred solution to contract size limits.

4 Likes

Yeah that would definitely works

2 Likes

Anyway I find out that even if I remove all the code from the main contract except for the constructor it still fails to deploy unfortunately. If I replace the ERC777 constructor with the ERC20Detailed one it works properly.

2 Likes

Has this been solved?

I’ve had issue with this limit as well with my current project. You definitely want the compiler optimization on. If you toggle optimization on and off the code size will change (if it doesn’t you should look elsewhere for the issue). Breaking up the contract has not worked for me in the past, as inherited code gets injected in the contract to be deployed. Using an EVM seems like a solution, or deploying multiple contracts that call each other. My issue was that I was compiling without optimization, when I engaged it I was able to deploy a contract with a lot of code in it. So unless your contract is very large, which it doesn’t seem likely from the imports listed above, there may be another issue.

Have you put the contract in Remix and played with different complier versions?

4 Likes

@tobyjaguar there were some issues with the ERC777 I suppose, because optimization was alredy turned on.

3 Likes