Enable/disable language features to save gas

Hi everyone,
I’m working on highly optimized contracts where the focus is gas efficiency.

Some default features such as checked indexing of arrays, zeroing of memory and (since solidity 0.8) checked arithmetic makes it harder to optimize contracts. I’m paying gas for something I don’t need.

Maybe there could be a way to enable/disable compiler features, either using pragma or compiler flags. Having the safest behavior enabled by default is newcomers friendly, so how about something along the lines of:

solc --optimize --opt-unchecked-arithmetic --opt-unchecked-index Contract.sol

or:

pragma solidity 0.8.1;
pragma unchecked_arithmetic;
pragma unchecked_indexing;
...

I love that we have one de-facto language for the EVM because this means more brains on the same compiler and a bigger community.

Edit: I think compiler flags are better than pragma.

1 Like

I would love to have this possibility.

I would probably prefer a compiler flag (similar to optimization), but I don’t have strong preference.

Another option for arrays could be (even so I am not sure if I really like it) to use a special type annotation (similar to the location): uint256[] memory unchecked

Aside from index checks, this another example of a check I wish to disable: (from the output of --ir-optimized)

function array_allocation_size_t_array$_t_address_$dyn(length) -> size
{
    if gt(length, 0xffffffffffffffff) { panic_error_0x41() }
    size := add(mul(length, 0x20), 0x20)
}

I think the general approach towards this should be to improve the Yul optimizer to handle cases where it can infer that some checks can be removed.

If you are talking about storage arrays and repeated sload for the length, the Yul optimizer (for the upcoming codegen) can improve some situations to do only a single sload. For more details, see arr.length question from Optimizer AMA. In the future, this could also be improved.

However, if you are talking about absolutely no checks for arrays, i.e., simply assign or read an index without even checking for the length, I don’t think the compiler should allow it so easily.

In use-cases such as yours, inline assembly might be the way ahead. You could write the array access functions in inline assembly. For unchecked, you could use the unchecked block. I agree that it won’t look pleasing, but such contracts would be rather rare, right?

Is the length known at compile time?

The length is not necessarily known at compile time and it’s a memory array.

“I don’t think the compiler should allow it so easily”

Why not? That’s how we’ve been coding C/C++ for a lifetime. And also, optimization flags have always existed. Public contracts just won’t use them.

I think solidity should provide good default so that people new to programming can write safer contracts but still have an option to disable the overhead as a compiler flag.

Would it be a lot of work in the compiler? I’ve been thinking about contributing for a while so I’m interested in helping implement this.

1 Like

This is interesting because we have had several forced checks by the complier for a long time and my impression that this has come up only recently due to the unchecked block. The most expensive check by far is probably the “extcodesize” check. There are also tons of checks for sanity of inputs in the ABI decoder. Where exactly does it matter so much that the code is as cheap as possible? I know the gas costs are high, but this has always been the case (relatively) and cutting back on safety should not be an answer to high gas costs, in my opinion. The checks are not only for “newcomers”, they are also for “seniors” that make just about the same number of mistakes, only that they feel safer.
If you disable safety checks, this should really stand out and should neither be possible via a compiler flag nor an easy to miss file-wide option.

1 Like

Maybe you hear about it from me only know because I started working on optimization only recently :grin:

Contrary to usual compilers, every bit of code costs money, not fractions of nanoseconds.

Current gas price on etherscan is 127 Gwei.

So, taking the amount of gas I managed to optimize last week for instance:

4000 gas units cost 0.000508eth = $0.9

$0.9 * 100 tx/day = $90 /day

Well contracts that are exploited due to crummy, unsafe, exploitable code can cost quite a bit too, in real money. I’m not saying that your code is any of that, and nor am I implying it, but this does happen and it is hard to include potential losses of that type in the equation.

Sure but that’s a choice that I people should be able to make for themselves.

I’m not saying change the default, I’m saying please also provide a way out.

1 Like

The alternative that currently exists is that devs start using assembly. Not sure if that is better.

If I have a way to disable some specific checks I would stick to solidity, making it easier to audit and understand for other developers.

Does it make sense to make the disabling very explicit? Probably.
Arguing from the “it should be easy to audit” standpoint, it should probably not be a compiler flag as this would not be directly visible in the source code.

We had a recent talk with our auditors about this, as we had a contract where we heavily relied on assembly to optimize for gas. His response was I think assembly implementation makes the same code harder to validate, but not exponentially so and it will reduce the amount of people that are able to validate the code.

I do think gas optimizations are quite important when it comes to recurring operations. E.g. validating a set of orders for an exchange. If you have 1000 orders you want to validate and can reduce the gas costs of each order validation by 1000 … that is a lot of gas. Also gas optimization probably becomes more important when projects reachm ore mature stages. Early versions of the contracts (that also have limited users) don’t need to be as optimized as “final” versions.

Would it be an option to use assembly isolated to Solidity functions? I guess the problem here is again that templates would be very useful.

Also some contracts are just a mean to execute a sequence of operations atomically (in the same transaction) and not exposed to the public. In these cases, the input is 100% controlled by the entity owning the contract and argument validation is not required, aside from something like require(msg.sender == ...)

Regarding inline assembly. Yes it could do the job, but not inside isolated solidity functions. – I would need a way to be able to call solidity functions from inline assembly so that optimized code can do all the heavy work and delegate a piece of business logic that needs audit to a solidity function written by someone else. Also some syntactic sugar to access array and struct elements would make the code a lot more readable.

But at that point, it’s more a need for “inline solidity” rather than “inline assembly”. Think having to implement everything in the fallback function as inline assembly, including manual parsing of the (tightly packed) arguments, and then dispatching some calls to solidity.

I think neither a compiler flag, nor a pragma would solve the readability and auditability problem. Also disabling all checks sounds dangerous and such contracts would be hard to even audit.

However, for some cases like storage arrays, it makes sense to introduce a member, say, unsafeAt. For example, for a storage array arr, arr.unsafeAt(i) would access the index i without bound checks.

One big issue with using a global compiler flag is importing shared code (for example a library from OZ). In this case it is quite possible that the imported library relies on implicit checks, and removing them globally would cause things to break in subtle ways.

I want to add a +1 for:

pragma unchecked_arithmetic;

I’m updating some code to 0.8.0, and some files (such as Math64x64) should have arithmetic checking completely disabled.

It’s much easier to add this pragma than to add unchecked blocks to every function.

Can you link to some example code, please? Have you considered writing it completely in assembly for efficiency?

Is there a way to easily get the storage slot where the array starts? I was looking at this recently and I had to run keccak256 myself to get the slot number. If using assembly is the recommendation I think it could be made easier for people who really want to do this.

Currently, you would need to find it by doing a keccak256 on arr.slot.
This is how solidity does it:

function array_dataslot_t_array$_t_uint256_$dyn_storage(ptr) -> data {
    data := ptr
    mstore(0, ptr)
    data := keccak256(0, 0x20)
}

Do you have a proposal to make this easier?

I was thinking it could be available as something like array.data_slot.

In some cases this slot is even statically known, when array is a state variable. I’m not sure if the optimizer would catch that from assembly like the one you shared.