What The Heck Is Opcode
To build fundamental knowledge about Ethereum Virtual Machine (EVM) and gain better design thinking while writing smart contracts, you need to learn about the EVM essentials. In solidity, being aware of what is happening under the hood will help you write more efficient smart contracts and write gas efficient codes. Ethereum opcodes are one of the fundamentals of EVM that you will learn through this article.

From Solidity To Opcode
The solidity compiler compiles smart contracts written in high-level programming language solidity to bytecodes. Bytecode is a series of opcodes that will run on EVM. Each opcode is 1 byte, if you do the math you can see that we can have 256 different opcodes. In this article, we figure out what will happen when you deploy your contract and how the EVM runs the code when you call a function.
Deploy a Simple Smart Contract
For educational purposes, I keep this part as simple as possible and deploy a small smart contract. We’re going to write a smart contract that only stores a variable with setter and getter functions.
Copy this code to EVM Playground, make sure you’ve selected solidity, and then hit run. On the top right part of the main window, there’s a play button. Click on it and wait until you see the contract’s bytecode. Actually, when you deploy a contract, you deploy the hexadecimal representation of it on the chain. So, you need to think hexadecimal to understand what’s happening inside EVM.

Here’s the generated bytecode:
608060405234801561001057600080fd5b50600436106100365760003560e01c806360fe47b11461003b5780636d4ce63c14610057575b600080fd5b610055600480360381019061005091906100c3565b610075565b005b61005f61007f565b60405161006c91906100ff565b60405180910390f35b8060008190555050565b60008054905090565b600080fd5b6000819050919050565b6100a08161008d565b81146100ab57600080fd5b50565b6000813590506100bd81610097565b92915050565b6000602082840312156100d9576100d8610088565b5b60006100e7848285016100ae565b91505092915050565b6100f98161008d565b82525050565b600060208201905061011460008301846100f0565b9291505056fea264697066735822122052387856f29f83d0d1896f55f86cd83fea2782cb01fa5f3c5937bf2711e6c3ce64736f6c634300080f0033
Setter Function
Now that we have the contract’s bytecode, we can check how EVM decides which function to run. The bytecode contains all of the smart contract’s functions in it. A function signature is the first 4 bytes of the “keccak” hash of the function. So let’s find it.
In “ethersjs” you can find the data with the following snippet:
const Store = await ethers.getContractFactory("Store");
const sig = Store.interface.encodeFunctionData('set', [100]);
console.log(sig);
The result will be the following hash:
0x60fe47b10000000000000000000000000000000000000000000000000000000000000064
if we separate the first 4 bytes will have the function signature:
0x60fe47b1
Now that we have the function signature, we’re one step closer to finding how EVM decides which function to run! To see our progress, just search the signature within the contract’s bytecode.
Now The Opcode
We found the calldata of the set function in the previous step. Now let’s see what happens when we call the function. Below you can see a list of opcodes that finds if you are reaching the set function.
PUSH1 0x80
PUSH1 0x40
MSTORE
CALLVALUE
DUP1
ISZERO
PUSH2 0x0010
JUMPI
PUSH1 0x00
DUP1
REVERT
JUMPDEST
POP
PUSH1 0x04
CALLDATASIZE
LT
PUSH2 0x0036
JUMPI
PUSH1 0x00
CALLDATALOAD
PUSH1 0xe0
SHR
DUP1
PUSH4 0x60fe47b1
EQ
PUSH2 0x003b
JUMPI
Now let’s walk through the opcodes.
PUSH1 0x80
PUSH1 0x40
MSTORE
The purpose of these opcodes is to save the free memory pointer. It happens on each function call. The EVM needs to know the free slot of memory to store values. The MSTORE
upcode takes a value and stores it in the defined offset. Here the value is 0x80 and the offset is 0x40.
CALLVALUE
DUP1
ISZERO
PUSH2 0x0010
JUMPI
PUSH1 0x00
DUP1
REVERT
JUMPDEST
This block of opcodes reads the amount of ETH sent to the contract (payable functions). If it’s not zero the JUMPI
opcode won’t happen and the transaction reverts!
We have seen two types of PUSH
opcodes, PUSH1
and PUSH2
. The PUSH1
opcode pushed a 1-byte value to the stack, the PUSH2
pushes a 2-byte value, and so on. Finally the PUSH32
pushes a 32-byte value (uint256) to the stack.
POP
PUSH1 0x04
CALLDATASIZE
LT
PUSH2 0x0036
JUMPI
The POP
opcode clears the stack. Then it checks if the calldata size is more than 4. Remember the function signature is 4 bytes.
CALLDATALOAD
PUSH1 0xe0
SHR
The CALLDATALOAD
opcode loads the calldata
to the stack. PUSH1 0xe0
and SHR
shift the calldata to the right by 0xe0 which is 224. 256 — 224 = 32
and 32 bits is 4 bytes. The result of these opcodes is the function signature!
DUP1
PUSH4 0x60fe47b1
EQ
PUSH2 0x003b
JUMPI
Now that the EVM has the function signature it tries to match it with the signatures that are in the contract’s bytecode. The first function is set(uint256)
so the EVM pushes its signature 0x60fe47b1
to the stack and compares it with the result of the previous code block. The EQ
opcode pushes 1 to the stack since both signatures are the same. Then the EVM pushes the starting offset of the set function on top of the stack and the JUMPI
happens. Now we’re in the body of the set function.
I highly recommend you copy the full opcode to the EVM Playground and test it with different calldatas and values!
Where to Go From Here
Now, you have enough information about how to follow the contract’s bytecode. You also know the basics of opcodes and how the EVM uses them. From here I recommend you to use Remix IDE, copy and paste this article’s contract and debug the get transaction. It will help you to remember the concept of this article. Learning EVM opcodes helps you to gain fundamental knowledge about solidity, EVM, and smart contracts.