7

I have the following solidity code:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

contract SSimpleStorage {
    uint256 storedNumber;

    function storeNumber(uint256 newNumber) external {
        storedNumber = newNumber;
    }

    function readNumber() external view returns (uint256) {
        return storedNumber;
    }
}

As you can see, there are no events being emitted.

However, when I compile this, I get the following opcode output:

Compile Command

solc --optimize --optimize-runs 20000 src/solidity/SSimpleStorage.sol --opcodes

Output:

PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0xAC DUP1 PUSH2 0x1E PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH1 0x32 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0xB6339418 EQ PUSH1 0x37 JUMPI DUP1 PUSH4 0xB63D343F EQ PUSH1 0x49 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x47 PUSH1 0x42 CALLDATASIZE PUSH1 0x4 PUSH1 0x5E JUMP JUMPDEST PUSH1 0x0 SSTORE JUMP JUMPDEST STOP JUMPDEST PUSH1 0x0 SLOAD PUSH1 0x40 MLOAD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 DUP5 SUB SLT ISZERO PUSH1 0x6F JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP CALLDATALOAD SWAP2 SWAP1 POP JUMP INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 GT SLT DUP9 0xE4 ADDMOD PUSH3 0x74EE3F 0xB3 STOP 0xE8 SWAP6 0xA6 SWAP6 SHL 0x5E 0xAB 0xAE PUSH18 0xA4832B2C422CCC5D7622CEEB64736F6C6343 STOP ADDMOD 0xF STOP CALLER 

As you can see, there is a LOG2 opcode in there. What is that doing there?

More information

I can pretty much account for 80% of what the binary/opcodes are doing. These opcodes in binary look like:

6080604052348015600f57600080fd5b5060ac8061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c8063b6339418146037578063b63d343f146049575b600080fd5b60476042366004605e565b600055565b005b60005460405190815260200160405180910390f35b600060208284031215606f57600080fd5b503591905056fea2646970667358221220111288e4086274ee3fb300e895a6951b5eabae71a4832b2c422ccc5d7622ceeb64736f6c634300080f0033

However, it's the last section with the LOG2 that are confusing.

Here is what it looks like the opcodes do so far:

Contract Creation Code

Free memory pointer

6080604052

Check if ETH is sent with a function call, revert if so

348015600f57600080fd

Jumps here if no ETH is sent with constructor, and copies the runtime code to memory

5b5060ac8061001e600039

Returns the runtime code. The runtime code is what get placed onto the blockchain. In our contract creation transaction, this is where it ends.

6000f3fe

Runtime Code

Entering the Contract

Free memory pointer

6080604052

Check if ETH is sent with a function call, revert if so

348015600f57600080fd

Short calldata check, and if size is too small (not big enough to have a function selector), jump to the fallback function (none)

5b5060043610603257

Get function seletor

60003560e01c

Check to see if the function selector is b6339418 (storeNumber), and store the jump location on the stack if so

8063b633941814603757

Check to see if the function selector is b63d343f (readNumber), and store the jump location on the stack if so

8063b63d343f14604957

Function Bodies & Wrappers

You'll notice they all start with 5b, which is the JUMPDEST opcode, meaning these are each jump destinations.

Fallback function... there isn't one, so revert

5b600080fd

Unpack the calldata, but we actually jump below to do some calldata validation

5b60476042366004605e56

storeNumber jump destination

Write to storage slot 0, and end the call

5b600055565b00

readNumber jump destination

Read from storage slot 0

It has to move memory around to have it return from the stack, and then ends the call with a return.

5b60005460405190815260200160405180910390f3

Calldata validation

We check to make sure our calldata is the right size (our store number isn't too big. This is different than the check above which checked for a function selector based on size. )

5b600060208284031215606f57

If it is too big, we revert

600080fd

The function body jump location is currently low on the stack, bubble it up and jump to it

5b503591905056

Unsure part

If you reach this point, obviously something has gone wrong, but there is a lot of code here, and I'm not sure what it's for.

Extra credit for helping me figure out what this whole thing is for, but I bet if we understand why there is a LOG command in there, it will help clear this up.

(a2 is the binary for the LOG2opcode)

fea2646970667358221220111288e4086274ee3fb300e895a6951b5eabae71a4832b2c422ccc5d7622ceeb64736f6c634300080f0033
2
  • Really insteresting. I'm not sure either, but I think it has to do with the readNumber() function returning a state variable. I tried returning a hard-coded value 5 and it did not produce that LOG2 opcode. I also tried removing the return statement altogether and it did not put that LOG2 either. Only when returning the state variable value storedNumber it adds the LOG2 opcode. Then I added another function returning the same state variable and it didn't add any LOG2. So, this is really weird. I bookmarked this question to get notified when someone adds an answer for this. Sep 7, 2022 at 15:27
  • 1
    Ooo, if you compile it with the --asm flag, you can see that lump of code that starts with a2 is in the auxdata section. I guess we need to learn more about auxdata. Sep 7, 2022 at 15:29

1 Answer 1

4

I think that's not actually code, I think that's the metadata hash also known as the auxdata, and it starts with a2:

a2646970667358221220111288e4086274ee3fb300e895a6951b5eabae71a4832b2c422ccc5d7622ceeb64736f6c634300080f0033

It is unreachable code that describes the compile environment of your contract, you can see it if you compile your code with the --asm flag.

solc --optimize --optimize-runs 20000 src/solidity/SSimpleStorage.sol --asm
2
  • How does solidity know that the metadata has started? Sep 7, 2022 at 15:38
  • 2
    Solidity doesn't have to know. The bytecode is executed in the EVM and to it this is simply unreachable code. You can disassemble the bytecode here to see that it is outside any reachable block: ethervm.io/decompile
    – patrickd
    Sep 7, 2022 at 15:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.