During execution, bytecode is split up into its bytes (1 byte equals 2 hexadecimal characters). Bytes in range 0x60–0x7f (PUSH1-PUSH32) are treated differently because they include push data (which needs to be attached to the opcode, instead of being treated as a separate opcode).

The first instruction is 0x60 which translates to PUSH1. Therefore, we know the push data is 1 byte long, and we add the next byte to the stack. The stack now contains 1 item, and we can move to the next instruction. Since we know 0x01 is part of a PUSH instruction, the next instruction we need to execute is another 0x60 (PUSH1) along with the same data. The stack now contains 2 identical items. The final instruction is 0x01 , which translates to ADD. This instruction will take 2 items from the stack, and pushes the sum of these items to the stack. The stack now contains one item: 0x02

Contract state

While many popular top-level programming languages allow users to directly pass arguments to functions ( function(argument1,argument2) ), lower-level programming languages often use a stack to pass values to functions. The EVM uses a 256-bit register stack from which the most recent 16 items can be accessed or manipulated at once. In total, the stack can only hold 1024 items.

Because of these limitations, complicated opcodes instead use contract memory to retrieve or pass data. However, memory is not persistent. When contract execution finishes, the memory contents will not be saved. Thus, while stack can be compared to function arguments, memory can be compared to declaring variables.

In order to store data indefinitely and make it accessible for future contract executions, one can use storage. Contract storage essentially acts as a public database, from which values can be read externally without having to send a transaction to the contract (no fees!). However, writing to storage is very expensive (as much as 6000x) compared to writing to memory.

Costs of interacting with smart contracts

Since all contract executions are run by everyone running an Ethereum node, an attacker could try creating contracts including lots of computationally expensive operations to slow down the network. To prevent such attacks from happening, every opcode has its own base gas cost. Furthermore, several complicated opcodes also charge a dynamic gas cost. For instance, the opcode KECCAK256 (formerly known as SHA3) has a base cost of 30 gas, and a dynamic cost of 6 gas per word (words are 256-bit items). Computationally expensive instructions charge a higher gas fee than simple, straightforward instructions. On top of that, every transaction starts at 21000 gas.

When executing instructions which reduce state size, gas can also be refunded. Setting a storage value to zero from non-zero refunds 15000 gas, while completely removing a contract (using the SELFDESTRUCT opcode) refunds 24000 gas. Refunds only occur after contract execution has completed, thus contracts cannot pay for themselves. Additionally, a refund cannot exceed half the gas used for the current contract call.

If you’re interested in reading more about gas, feel free to take a look at this excellent article: “What is Gas?”

Deploying a smart contract

When deploying a smart contract, a regular transaction is created, without a to address. Additionally, some bytecode is added as input data. This bytecode acts as a constructor, which is needed to write initial variables to storage before copying the runtime bytecode to the contract’s code. During deployment, creation bytecode will only run once, while runtime bytecode will run on every contract call.

Another example Solidity contract, along with its bytecode needed to deploy

We can split up the bytecode shown above into three pieces:

Constructor