Ruby Quiz

Ruby Quiz - Challenge #11 - Blockchain Contracts - Disassemble & Assemble Ethereum Virtual Machine (EVM) Opcodes / Bytecodes

Ethereum - the decentralized “world computer” - lets you store and run your own (contract) code on the blockchain.

Remember the “magic” sooper-sekret gene mixing operation formula in the CryptoKitties GeneSciene contract?

You can find the “raw” bytecode encoded as a (hex) string for the virtual stack machine in the contract at etherscan.io/address/0xf97e0a5b616dffc913e72455fde9ea8bbe946a2b#code.

0x60606040526004361061006c5763ffffffff7c0100000000000000000000000000000000000000
0000000000000000006000350416630d9f5aed81146100715780631597ee441461009f57806354c1
5b82146100ee57806361a769001461011557806377a74a201461017e575b600080fd5b341561007c
57600080fd5b61008d6004356024356044356101cd565b60405190815260200160405180910390f3
...
52600019909101906020018161072b57905050905600

If you click on “Switch to Opcodes” you will see an almost endless stream of to-the-metal Ethereum stack machine instructions:

PUSH1 0x60
PUSH1 0x40
MSTORE
PUSH1 0x04
CALLDATASIZE
LT
PUSH2 0x006c
JUMPI
PUSH4 0xffffffff
PUSH29 0x0100000000000000000000000000000000000000000000000000000000
PUSH1 0x00
CALLDATALOAD
DIV
AND
PUSH4 0x0d9f5aed
DUP2
EQ
PUSH2 0x0071
JUMPI
DUP1
PUSH4 0x1597ee44
EQ
PUSH2 0x009f
JUMPI
DUP1
PUSH4 0x54c15b82
...

The challenge: Code a disassemble and an assemble method that pass the RubyQuizTest :-).

For the starter level 1 turn the hex string into a series of opcodes with the disassemble method.

def disassemble( hex )
  # ...
end

And for the bonus level 2 turn the series of opcodes back into a hex string with the assemble method.

def assemble( code )
  # ...
end

Start from scratch or, yes, use any library / gem you can find. To help along the test includes all opcodes / bytecodes:

module Ethereum
  class Opcodes

    TABLE = {
      0x00 => :STOP,
      0x01 => :ADD,
      0x02 => :MUL,
      0x03 => :SUB,
      0x04 => :DIV,
      0x05 => :SDIV,
      0x06 => :MOD,
      0x07 => :SMOD,
      0x08 => :ADDMOD,
      0x09 => :MULMOD,
      0x0a => :EXP,
      0x0b => :SIGNEXTEND,
      0x10 => :LT,
      0x11 => :GT,
      0x12 => :SLT,
      0x13 => :SGT,
      0x14 => :EQ,
      0x15 => :ISZERO,
      0x16 => :AND,
      0x17 => :OR,
      0x18 => :XOR,
      0x19 => :NOT,
      0x1a => :BYTE,
      0x20 => :KECCAK256,   ## note: SHA3 in "older" tables
      0x30 => :ADDRESS,
      0x31 => :BALANCE,
      0x32 => :ORIGIN,
      0x33 => :CALLER,
      0x34 => :CALLVALUE,
      0x35 => :CALLDATALOAD,
      0x36 => :CALLDATASIZE,
      0x37 => :CALLDATACOPY,
      0x38 => :CODESIZE,
      0x39 => :CODECOPY,
      0x3a => :GASPRICE,
      0x3b => :EXTCODESIZE,
      0x3c => :EXTCODECOPY,
      0x40 => :BLOCKHASH,
      0x41 => :COINBASE,
      0x42 => :TIMESTAMP,
      0x43 => :NUMBER,
      0x44 => :DIFFICULTY,
      0x45 => :GASLIMIT,
      0x50 => :POP,
      0x51 => :MLOAD,
      0x52 => :MSTORE,
      0x53 => :MSTORE8,
      0x54 => :SLOAD,
      0x55 => :SSTORE,
      0x56 => :JUMP,
      0x57 => :JUMPI,
      0x58 => :PC,
      0x59 => :MSIZE,
      0x5a => :GAS,
      0x5b => :JUMPDEST,
      0xa0 => :LOG0,
      0xa1 => :LOG1,
      0xa2 => :LOG2,
      0xa3 => :LOG3,
      0xa4 => :LOG4,
      0xf0 => :CREATE,
      0xf1 => :CALL,
      0xf2 => :CALLCODE,
      0xf3 => :RETURN,
      0xf4 => :DELEGATECALL,
      0xf5 => :CREATE2,
      0xff => :SUICIDE,
      0xfd => :REVERT,
      0xfe => :INVALID,
    }

    32.times do |i|
      TABLE[0x60+i] = :"PUSH#{i+1}"
    end

    16.times do |i|
      TABLE[0x80+i] = :"DUP#{i+1}"
      TABLE[0x90+i] = :"SWAP#{i+1}"
    end


    REVERSE_TABLE = TABLE.reduce({}) do |table, (opcode, name)|
      table[name] = opcode
      table
    end
  end # class Opcodes
end # module Ethereum

Use:

Ethereum::Opcodes::TABLE[0x60]  #=> :PUSH1
Ethereum::Opcodes::TABLE[0x61]  #=> :PUSH2
Ethereum::Opcodes::TABLE[0x00]  #=> :STOP

to lookup the instruction name from the bytecode (e.g. 0x60, 0x61, 0x00, etc.). And use:

Ethereum::Opcodes::REVERSE_TABLE[:PUSH1]  #=> 0x60
Ethereum::Opcodes::REVERSE_TABLE[:PUSH2]  #=> 0x61
Ethereum::Opcodes::REVERSE_TABLE[:STOP]   #=> 0x00

to lookup the bytecode from the instruction name (e.g. PUSH1, PUSH2, STOP).

Note: In the hex string every byte is a bytecode instruction with the ONLY exception of PUSH1, PUSH2, PUSH3 .. PUSH32. The PUSH1 instruction places the following 1-byte on the stack (e.g. disassemble to PUSH1 0x60). The PUSH2 instruction places the following 2-bytes on the stack (e.g. disassemble to PUSH4 0x006c). The PUSH29 instruction places the following 29-bytes on the stack (e.g. disassemble to PUSH29 0x0100000000000000000000000000000000000000000000000000000000 ) and so on.

TIP: If interested read more about the Ethereum Virtual Machine (EVM) and the opcodes / bytcodes in the (free online) “The Ethereum Virtual Machine” chapter in the Mastering Ethereum book by Andreas M. Antonopoulos and Gavin Wood.

To qualify for solving the code challenge / puzzle you must pass the test:

require 'minitest/autorun'

require_relative './opcodes'


class RubyQuizTest < MiniTest::Test

  def mixgenes_hex
     hex = <<TXT
0x60606040526004361061006c5763ffffffff7c0100000000000000000000000000000000000000
0000000000000000006000350416630d9f5aed81146100715780631597ee441461009f57806354c1
5b82146100ee57806361a769001461011557806377a74a201461017e575b600080fd5b341561007c
57600080fd5b61008d6004356024356044356101cd565b60405190815260200160405180910390f3
5b34156100aa57600080fd5b61008d60046024813581810190830135806020818102016040519081
0160405280939291908181526020018383602002808284375094965061055a95505050505050565b
34156100f957600080fd5b61010161059d565b604051901515815260200160405180910390f35b34
1561012057600080fd5b61012b6004356105a6565b60405160208082528190810183818151815260
200191508051906020019060200280838360005b8381101561016a57808201518382015260200161
0152565b505050509050019250505060405180910390f35b341561018957600080fd5b6101946004
3561061e565b604051808261018080838360005b838110156101ba57808201518382015260200161
01a2565b5050505090500191505060405180910390f35b60008060006101da610709565b6101e261
0709565b6101ea610709565b60008080808080438d90116101fe57600080fd5b8c409a508a151561
022b5760ff8d1660ff194316019c50438d101515610226576101008d039c505b8c409a505b8a8f8f
8f604051808581526020018481526020018381526020018281526020019450505050506040519081
900390209a50600099506102698f6105a6565b98506102748e6105a6565b97506030604051805910
6102855750595b90808252806020026020018201604052509650600093505b600c8410156103ee57
600392505b600183106103e35782846004020195506102c78b60028c610668565b915060028a0199
508160001415610349578886815181106102e457fe5b906020019060200201519450886001870381
5181106102ff57fe5b9060200190602002015189878151811061031557fe5b60ff90921660209283
0290910190910152848960001988018151811061033757fe5b60ff90921660209283029091019091
01525b6103558b60028c610668565b915060028a01995081600014156103d7578786815181106103
7257fe5b90602001906020020151945087600187038151811061038d57fe5b906020019060200201
518887815181106103a357fe5b60ff90921660209283029091019091015284886000198801815181
106103c557fe5b60ff9092166020928302909101909101525b600019909201916102ab565b600190
93019261029d565b600095505b603086101561053e57506000600486061580156104405750878681
51811061041757fe5b9060200190602002015160011689878151811061043057fe5b906020019060
2002015160011614155b15610491576104518b60038c610668565b915060038a01995061048e8987
8151811061046857fe5b9060200190602002015189888151811061047e57fe5b9060200190602002
01518461067e565b90505b60008160ff1611156104c057808787815181106104aa57fe5b60ff9092
16602092830290910190910152610533565b6104cc8b60018c610668565b915060018a0199508160
0014156104ff578886815181106104e957fe5b906020019060200201518787815181106104aa57fe
5b87868151811061050b57fe5b9060200190602002015187878151811061052157fe5b60ff909216
6020928302909101909101525b6001909501946103f3565b6105478761055a565b9f9e5050505050
50505050505050505050565b6000805b60308110156105975760209091029082602f829003815181
1061057d57fe5b9060200190602002015160ff16919091179060010161055e565b50919050565b60
005460ff1681565b6105ae610709565b6105b6610709565b600060306040518059106105c7575059
5b90808252806020026020018201604052509150600090505b6030811015610617576105f2848261
06f1565b8282815181106105fe57fe5b60ff9092166020928302909101909101526001016105df56
5b5092915050565b61062661071b565b61062e61071b565b60005b600c8110156106175761064784
826004026106f1565b8282600c811061065357fe5b60ff9092166020929092020152600101610631
565b600290810a91900a600019018102919091160490565b600083838260ff808316908416111561
0698578691508592505b82820360ff1660011480156106b65750600260ff84160660ff166000145b
156106e75760178360ff1610156106cf575060016106d3565b5060005b8085116106e757600260ff
84160460100193505b5050509392505050565b600061070283600584600502610668565b93925050
50565b60206040519081016040526000815290565b610180604051908101604052600c815b600081
52600019909101906020018161072b57905050905600
TXT

  hex = hex.gsub( /[ \t\n\r]/, '' )  ## remove all whitespaces and newlines (/r/n)
  hex
end

def mixgenes_code
  code = File.read( './mixgenes.opcodes' )
  code = code.strip   ## note: strip trailing newline
  code
end



def test_mixgenes_level1
  assert_equal mixgenes_code, disassemble( mixgenes_hex )
end

def test_mixgenes_level2
  assert_equal mixgenes_hex,  assemble( mixgenes_code )
end


def test_mixgenes
  assert_equal mixgenes_hex,  assemble( disassemble( mixgenes_hex ))
end

end # class RubyQuizTest

Post your code snippets on the “official” Ruby Quiz Channel, that is, the ruby-talk mailing list.

Happy opscode hacking and blockchain contract bytecode assembling & disassembling with Ruby.