How to use Yul Verbatim with Solidity

By akohad Oct18,2022

[ad_1]

The solidity compiler isn’t very efficient. Often for elementary operations, the Solidity compiler will add a ton of unnecessary opcodes. The compiler does this because it adds a lot of checks if the developer didn’t screw up, and if they did, the compiler would revert before doing any damage.

This is great when you start with solidity and just want to write something that works. But since gas on Ethereum can get very expensive, sometimes we need to optimize the contract as much as possible.

You can use inline assembly, but the solidity compiler still adds many checks and unnecessary opcodes that just cost more gas.

You can also use language like Huff, which is excellent. It gives you access to the stack, and you can do any optimization you want. But writing a complex contract in Huff is torture since you have to go opcode by opcode.

The best option, in my opinion, and the topic of this article, is to write the contract in solidity, leave out the functions that need more optimization, and just add the bytecode with verbatim later.

What is Verbatim?

Per the Solidity docs:

The set of verbatim… builtin functions lets you create bytecode for opcodes that are not known to the Yul compiler. It also allows you to create bytecode sequences that will not be modified by the optimizer.

In short, it will allow us to add our own bytecode to an already compiled contract. But there is a small catch. Verbatim doesn’t work in solidity, so we will first have to compile our contract down to YUL and then add the bytecode with verbatim.

How to generate the optimized bytecode?

Some tools let you do this, but they are pretty complicated, so I wrote a simple compiler that will generate the bytecode for you.

  1. Go to this Repo and clone it to your machine
git clone https://github.com/Kuly14/mnemonic.git

2. Cd into the folder

cd mnemonic

3. Create your .mn file like this

touch yourName.mn

4. Write your function in pure opcodes

Normally if you wrote in pure opcodes it should look something like this:

push1 0x04 calldataload
push1 0x00 mstore
push1 0x20
push1 0x00
return

Which would be annoying and time-consuming. So I added it to the compiler that the push opcodes are automatically added when you use 0x:

0x04 calldataload
0x00 mstore
0x20 0x00
return

So if you’ll try to use the push opcodes it won’t compile. Just write they bytes you want to push on stack with 0x.

5. Compile the .mn file with the compiler

cargo run -- /path/to/your/file/nameOfYourFile.mn

This will compile the contract and will save the bytecode to bytecode.txt

If you want the compiler to print the bytecode to the terminal use -p flag like this:

cargo run -- /path/to/your/file/nameOfYourFile.mn -p

6. Create your solidity contract and leave the function you want to write in opcodes empty

function show() public view returns(uint) {}

7. Compile your solidity file to YUL with solc

solc nameOfYourContract.sol --ir > nameOfYourNewYulFile.yul

This will compile the contract to YUL and will save it in nameOfYourNewYulFile.yul

8. Open the YUL file with your favorite editor and delete the first line

When you first open the file, the first line should look like this

IR:

Delete this line.

9. Find the function you left empty

Just ctrl F and write the whole function

ctrl F — function show() public view returns(uint)

There you should see something like this

10. Copy the bytecode from your bytecode.txt file and add it to the YUL file like this:

If you look at the verbatim statement after the verbatim, there is _0i_0o. This means that we will take 0 items of the stack into the verbatim and return 0 items to the stack from the verbatim.

This can come in handy if you just need to optimize a part of a function and you want to take items off the stack into the verbatim.

But that can get really complex really quickly. You would have to go through the function with the debugger and figure out what is on the stack at that particular moment.

If you don’t know how to use the remix debugger, I wrote an article that you can check out here: https://medium.com/@kulman.david/how-to-use-remix-debugger-to-learn-solidity-assembly-5a745a22bb07

11. Compile the YUL contract to bytecode

solc --strict-assembly nameOfYourYulFile.yul --bin

This will output the bytecode of the entire contract. Copy it and move on to the next step.

12. Deploy the bytecode

Now that we have the bytecode, we can deploy the contract.

The best way to do this is with hardhat. Go to this repo: https://github.com/Kuly14/deploy-bytecode and clone it to your machine:

git clone https://github.com/Kuly14/deploy-bytecode.git

12.1 Install dependencies:

Install all the necessary dependencies that we will need to deploy the contract:

yarn

Add your solidity contract to src folder , this is important so hardhat can generate the ABI.

12.2 Compile the contracts:

yarn hardhat compile

This command will generate the ABI inside the artifacts folder.

Now go to `test/unit/Deploy2.test.ts`

12.3 Import the ABI

import * as abi from ../../artifacts/path/to/your/abi.json

And also, change the `const bytecode = “0x6000…”` to your bytecode.

Don’t forget to add `0x` at the start of the bytecode, or it won’t work.

Now inside the `it` block, write your own tests as you can see I did here: https://github.com/Kuly14/deploy-bytecode/blob/main/test/unit/Deploy2.test.ts

12.4 Run your tests:

yarn hardhat test test/unit/Deploy2.test.ts

Congrats!!!

Here are links to both repos we used in this article:

If you get lost don’t hesitate to raise an issue or just leave a comment in this article. Thanks for reading!

CAUTION

This is very risky to do. We are messing with the stack manually which can create many attack vectors and make the contracts vulnerable. This is pretty much just for fun. Unless absolutely necessary, I would never use this in actual main-net code.

New to trading? Try crypto trading bots or copy trading

[ad_2]

Source link

By akohad

Related Post

Leave a Reply

Your email address will not be published. Required fields are marked *