EVM Execution

The EVM is a stack machine over some simple opcodes. Most of the opcodes are “local” to the execution state of the machine, but some of them must interact with the world state. This file only defines the local execution operations, the file ethereum.md will define the interactions with the world state.

requires "data.k"

module EVM
    imports STRING
    imports EVM-DATA

Configuration

The configuration has cells for the current account id, the current opcode, the program counter, the current gas, the gas price, the current program, the word stack, and the local memory. In addition, there are cells for the callstack and execution substate.

We’ve broken up the configuration into two components; those parts of the state that mutate during execution of a single transaction and those that are static throughout. In the comments next to each cell, we’ve marked which component of the yellowpaper state corresponds to each cell.

configuration <k> $PGM:EthereumSimulation </k>
              <exit-code exit=""> 1 </exit-code>
              <mode> $MODE:Mode </mode>
              <schedule> $SCHEDULE:Schedule </schedule>
              <analysis> .Map </analysis>

              <ethereum>

                // EVM Specific
                // ============

                <evm>

                  // Mutable during a single transaction
                  // -----------------------------------

                  <output>        .WordStack </output>              // H_RETURN
                  <memoryUsed>    0          </memoryUsed>          // \mu_i
                  <callDepth>     0          </callDepth>
                  <callStack>     .List      </callStack>
                  <interimStates> .List      </interimStates>
                  <substateStack> .List      </substateStack>
                  <callLog>       .Set       </callLog>

                  <txExecState>
                    <program>      .Map       </program>            // I_b
                    <programBytes> .WordStack </programBytes>

                    // I_*
                    <id>        0          </id>                    // I_a
                    <caller>    0          </caller>                // I_s
                    <callData>  .WordStack </callData>              // I_d
                    <callValue> 0          </callValue>             // I_v

                    // \mu_*
                    <wordStack>   .WordStack </wordStack>           // \mu_s
                    <localMem>    .Map       </localMem>            // \mu_m
                    <pc>          0          </pc>                  // \mu_pc
                    <gas>         0          </gas>                 // \mu_g
                    <previousGas> 0          </previousGas>
                  </txExecState>

                  // A_* (execution substate)
                  <substate>
                    <selfDestruct> .Set  </selfDestruct>            // A_s
                    <log>          .List </log>                     // A_l
                    <refund>       0     </refund>                  // A_r
                  </substate>

                  // Immutable during a single transaction
                  // -------------------------------------

                  <gasPrice> 0 </gasPrice>                          // I_p
                  <origin>   0 </origin>                            // I_o

                  // I_H* (block information)
                  <previousHash>     0          </previousHash>     // I_Hp
                  <ommersHash>       0          </ommersHash>       // I_Ho
                  <coinbase>         0          </coinbase>         // I_Hc
                  <stateRoot>        0          </stateRoot>        // I_Hr
                  <transactionsRoot> 0          </transactionsRoot> // I_Ht
                  <receiptsRoot>     0          </receiptsRoot>     // I_He
                  <logsBloom>        .WordStack </logsBloom>        // I_Hb
                  <difficulty>       0          </difficulty>       // I_Hd
                  <number>           0          </number>           // I_Hi
                  <gasLimit>         0          </gasLimit>         // I_Hl
                  <gasUsed>          0          </gasUsed>          // I_Hg
                  <timestamp>        0          </timestamp>        // I_Hs
                  <extraData>        .WordStack </extraData>        // I_Hx
                  <mixHash>          0          </mixHash>          // I_Hm
                  <blockNonce>       0          </blockNonce>       // I_Hn

                  <ommerBlockHeaders> [ .JSONList ] </ommerBlockHeaders>
                  <blockhash>         .List         </blockhash>

                </evm>

                // Ethereum Network
                // ================

                <network>

                  // Accounts Record
                  // ---------------

                  <activeAccounts> .Map </activeAccounts>
                  <accounts>
  • UIUC-K and RV-K have slight differences of opinion here.
<account multiplicity="*" type="Bag">
<account multiplicity="*" type="Map">
    <acctID>  0          </acctID>
    <balance> 0          </balance>
    <code>    .WordStack </code>
    <storage> .Map       </storage>
    <nonce>   0          </nonce>
  </account>
</accounts>

// Transactions Record
// -------------------

<txOrder>   .List </txOrder>
<txPending> .List </txPending>

<messages>
  • UIUC-K and RV-K have slight differences of opinion here.
<message multiplicity="*" type="Bag">
<message multiplicity="*" type="Map">
                         <msgID>      0          </msgID>
                         <txNonce>    0          </txNonce>            // T_n
                         <txGasPrice> 0          </txGasPrice>         // T_p
                         <txGasLimit> 0          </txGasLimit>         // T_g
                         <to>         .Account   </to>                 // T_t
                         <value>      0          </value>              // T_v
                         <v>          0          </v>                  // T_w
                         <r>          .WordStack </r>                  // T_r
                         <s>          .WordStack </s>                  // T_s
                         <data>       .WordStack </data>               // T_i/T_e
                       </message>
                     </messages>

                   </network>

                 </ethereum>

   syntax EthereumSimulation
// -------------------------

Hardware

The callStack cell stores a list of previous VM execution states.

  • #pushCallStack saves a copy of VM execution state on the callStack.
  • #popCallStack restores the top element of the callStack.
  • #dropCallStack removes the top element of the callStack.
   syntax State ::= "{" Int "|" Int "|" Map "|" WordStack "|" Int "|" WordStack "|" Int "|" WordStack "|" Map "|" Int "|" Int "|" Int "}"
// --------------------------------------------------------------------------------------------------------------------------------------

   syntax InternalOp ::= "#pushCallStack"
// --------------------------------------
   rule <k> #pushCallStack => . ... </k>
        <callStack>  (.List => ListItem({ ACCT | GAVAIL | PGM | BYTES | CR | CD | CV | WS | LM | MUSED | PCOUNT | DEPTH })) ... </callStack>
        <id>           ACCT   </id>
        <gas>          GAVAIL </gas>
        <program>      PGM    </program>
        <programBytes> BYTES  </programBytes>
        <caller>       CR     </caller>
        <callData>     CD     </callData>
        <callValue>    CV     </callValue>
        <wordStack>    WS     </wordStack>
        <localMem>     LM     </localMem>
        <memoryUsed>   MUSED  </memoryUsed>
        <pc>           PCOUNT </pc>
        <callDepth>    DEPTH  </callDepth>

   syntax InternalOp ::= "#popCallStack"
// -------------------------------------
   rule <k> #popCallStack => . ... </k>
        <callStack>  (ListItem({ ACCT | GAVAIL | PGM | BYTES | CR | CD | CV | WS | LM | MUSED | PCOUNT | DEPTH }) => .List) ... </callStack>
        <id>           _ => ACCT   </id>
        <gas>          _ => GAVAIL </gas>
        <program>      _ => PGM    </program>
        <programBytes> _ => BYTES  </programBytes>
        <caller>       _ => CR     </caller>
        <callData>     _ => CD     </callData>
        <callValue>    _ => CV     </callValue>
        <wordStack>    _ => WS     </wordStack>
        <localMem>     _ => LM     </localMem>
        <memoryUsed>   _ => MUSED  </memoryUsed>
        <pc>           _ => PCOUNT </pc>
        <callDepth>    _ => DEPTH  </callDepth>

   syntax InternalOp ::= "#dropCallStack"
// --------------------------------------
   rule <k> #dropCallStack => . ... </k> <callStack> (ListItem(_) => .List) ... </callStack>

The interimStates cell stores a list of previous world states.

  • #pushWorldState stores a copy of the current accounts at the top of the interimStates cell.
  • #popWorldState restores the top element of the interimStates.
  • #dropWorldState removes the top element of the interimStates.

We use slightly different rules for rv-k versus uiuc-k because uiuc-k does not support storing configuration fragments. The semantics of these two sets of rules are identical, however, the version rv-k uses is substantially faster. We would use that version in both places if not for technical limitations on the prover. In the long term, a new prover will be built capable of supporting the same code in both versions of the semantics.

   syntax Account ::= "{" Int "|" Int "|" WordStack "|" Map "|" Int "|" Bool "}"
// -----------------------------------------------------------------------------

   syntax InternalOp ::= "#pushWorldState" | "#pushWorldStateAux" Map | "#pushAccount" Int Bool
// --------------------------------------------------------------------------------------------
   rule <k> #pushWorldState => #pushWorldStateAux ACCTS ... </k>
        <activeAccounts> ACCTS </activeAccounts>
        <interimStates> (.List => ListItem(.Set)) ... </interimStates>

   rule <k> #pushWorldStateAux .Map => . ... </k>
   rule <k> #pushWorldStateAux ((ACCT |-> EMPTY) REST) => #pushAccount ACCT EMPTY ~> #pushWorldStateAux REST ... </k>
   rule <k> #pushAccount ACCT EMPTY => . ... </k>
        <interimStates> ListItem((.Set => SetItem({ ACCT | BAL | CODE | STORAGE | NONCE | EMPTY })) REST) ... </interimStates>
        <account>
          <acctID> ACCT </acctID>
          <balance> BAL </balance>
          <code> CODE </code>
          <storage> STORAGE </storage>
          <nonce> NONCE </nonce>
        </account>

   syntax InternalOp ::= "#popWorldState" | "#popWorldStateAux" Set
// ----------------------------------------------------------------
   rule <k> #popWorldState => #popWorldStateAux PREVSTATE ... </k>
        <interimStates> (ListItem(PREVSTATE) => .List) ... </interimStates>
        <activeAccounts> _ => .Map </activeAccounts>
        <accounts> _ => .Bag </accounts>

   rule <k> #popWorldStateAux .Set => . ... </k>
   rule <k> #popWorldStateAux ( (SetItem({ ACCT | BAL | CODE | STORAGE | NONCE | EMPTY }) => .Set) REST ) ... </k>
        <activeAccounts> ... (.Map => ACCT |-> EMPTY) </activeAccounts>
        <accounts> ( .Bag
                  => <account>
                       <acctID> ACCT </acctID>
                       <balance> BAL </balance>
                       <code> CODE </code>
                       <storage> STORAGE </storage>
                       <nonce> NONCE </nonce>
                     </account>
                   )
                   ...
        </accounts>

   syntax InternalOp ::= "#dropWorldState"
// ---------------------------------------
   rule <k> #dropWorldState => . ... </k> <interimStates> (ListItem(_) => .List) ... </interimStates>
   syntax Accounts ::= "{" AccountsCell "|" Map "}"
// ------------------------------------------------

   syntax InternalOp ::= "#pushWorldState"
// ---------------------------------------
   rule <k> #pushWorldState => .K ... </k>
        <activeAccounts> ACCTS </activeAccounts>
        <accounts> ACCTDATA </accounts>
        <interimStates> (.List => ListItem({ <accounts> ACCTDATA </accounts> | ACCTS })) ... </interimStates>

   syntax InternalOp ::= "#popWorldState"
// --------------------------------------
   rule <k> #popWorldState => .K ... </k>
        <interimStates> (ListItem({ <accounts> ACCTDATA </accounts> | ACCTS }) => .List) ... </interimStates>
        <activeAccounts> _ => ACCTS </activeAccounts>
        <accounts> _ => ACCTDATA </accounts>

   syntax InternalOp ::= "#dropWorldState"
// ---------------------------------------
   rule <k> #dropWorldState => . ... </k> <interimStates> (ListItem(_) => .List) ... </interimStates>

The substateStack cell stores a list of previous substate logs.

  • #pushSubstate stores a copy of the current substate at the top of the substateStack cell.
  • #popSubstate restores the top element of the substateStack.
  • #dropSubstate removes the top element of the substateStack.
   syntax InternalOp ::= "#pushSubstate"
// -------------------------------------
   rule <k> #pushSubstate => .K ... </k>
        <substate> SUBSTATE </substate>
        <substateStack> (.List => ListItem(<substate> SUBSTATE </substate>)) ... </substateStack>

   syntax InternalOp ::= "#popSubstate"
// ------------------------------------
   rule <k> #popSubstate => .K ... </k>
        <substate> _ => SUBSTATE </substate>
        <substateStack> (ListItem(<substate> SUBSTATE </substate>) => .List) ... </substateStack>

   syntax InternalOp ::= "#dropSubstate"
// -------------------------------------
   rule <k> #dropSubstate => .K ... </k> <substateStack> (ListItem(_) => .List) ... </substateStack>

Simple commands controlling exceptions provide control-flow.

  • #end is used to indicate the (non-exceptional) end of execution.
  • #exception is used to indicate exceptional states (it consumes any operations to be performed after it).
  • #?_:_?# allows for branching control-flow; if it reaches the front of the op cell it takes the first branch, if an exception runs into it it takes the second branch.
   syntax KItem ::= Exception
   syntax Exception ::= "#exception" | "#end"
// ------------------------------------------
   rule <k> EX:Exception ~> (_:Int    => .) ... </k>
   rule <k> EX:Exception ~> (_:OpCode => .) ... </k>

   syntax KItem ::= "#?" K ":" K "?#"
// ----------------------------------
   rule <k>                #? K : _ ?#  => K         ... </k>
   rule <k> #exception ~>  #? _ : K ?#  => K         ... </k>
   rule <k> #end       ~> (#? K : _ ?#) => K ~> #end ... </k>

OpCode Execution Cycle

OpCode is broken into several subsorts for easier handling. Here all OpCodes are subsorted into KItem (allowing sequentialization), and the various sorts of opcodes are subsorted into OpCode.

   syntax KItem  ::= OpCode
   syntax OpCode ::= NullStackOp | UnStackOp | BinStackOp | TernStackOp | QuadStackOp
                   | InvalidOp | StackOp | InternalOp | CallOp | CallSixOp | PushOp
// --------------------------------------------------------------------------------
  • #execute calls #next repeatedly until it recieves an #end or #exception.
  • #execTo executes until the next opcode is one of the specified ones.
   syntax KItem ::= "#execute"
// ---------------------------
   rule <k> (. => #next) ~> #execute ... </k>
   rule <k> EX:Exception ~> (#execute => .)  ... </k>

   syntax InternalOp ::= "#execTo" Set
// -----------------------------------
   rule <k> (. => #next) ~> #execTo OPS ... </k>
        <pc> PCOUNT </pc>
        <program> ... PCOUNT |-> OP ... </program>
     requires notBool (OP in OPS)

   rule <k> #execTo OPS => . ... </k>
        <pc> PCOUNT </pc>
        <program> ... PCOUNT |-> OP ... </program>
     requires OP in OPS

   rule <k> #execTo OPS => #end ... </k>
        <pc> PCOUNT </pc>
        <program> PGM </program>
     requires notBool PCOUNT in keys(PGM)

Execution follows a simple cycle where first the state is checked for exceptions, then if no exceptions will be thrown the opcode is run.

  • Regardless of the mode, #next will throw #end or #exception if the current program counter is not pointing at an OpCode.

TODO: I think on #next we are supposed to pretend it’s STOP if it’s in the middle of the program somewhere but is invalid? I suppose the semantics currently loads INVALID where N is the position in the bytecode array.

   syntax InternalOp ::= "#next"
// -----------------------------
   rule <k> #next => #end ... </k>
        <pc> PCOUNT </pc>
        <program> PGM </program>
     requires notBool (PCOUNT in_keys(PGM))
  • In NORMAL or VMTESTS mode, #next checks if the opcode is exceptional, runs it if not, then increments the program counter.
rule <mode> EXECMODE </mode>
     <k> #next
      => #pushCallStack
      ~> #exceptional? [ OP ] ~> #exec [ OP ] ~> #pc [ OP ]
      ~> #? #dropCallStack : #popCallStack ~> #exception ?#
     ...
     </k>
     <pc> PCOUNT </pc>
     <program> ... PCOUNT |-> OP ... </program>
  requires EXECMODE in (SetItem(NORMAL) SetItem(VMTESTS))

Exceptional OpCodes

Some checks if an opcode will throw an exception are relatively quick and done up front.

  • #exceptional? checks if the operator is invalid and will not cause wordStack size issues (this implements the function Z in the yellowpaper, section 9.4.2).
   syntax InternalOp ::= "#exceptional?" "[" OpCode "]"
// ----------------------------------------------------
   rule <k> #exceptional? [ OP ] => #invalid? [ OP ] ~> #stackNeeded? [ OP ] ~> #badJumpDest? [ OP ] ... </k>
  • #invalid? checks if it’s the designated invalid opcode.
   syntax InternalOp ::= "#invalid?" "[" OpCode "]"
// ------------------------------------------------
   rule <k> #invalid? [ INVALID ] => #exception ... </k>
   rule <k> #invalid? [ OP      ] => .          ... </k> requires notBool isInvalidOp(OP)
  • #stackNeeded? checks that the stack will be not be under/overflown.
  • #stackNeeded, #stackAdded, and #stackDelta are helpers for deciding #stackNeeded?.
   syntax InternalOp ::= "#stackNeeded?" "[" OpCode "]"
// ----------------------------------------------------
   rule <k> #stackNeeded? [ OP ] => #exception ... </k>
        <wordStack> WS </wordStack>
     requires #sizeWordStack(WS) <Int #stackNeeded(OP)
       orBool #sizeWordStack(WS) +Int #stackDelta(OP) >Int 1024

   rule <k> #stackNeeded? [ OP ] => .K ... </k>
        <wordStack> WS </wordStack>
     requires notBool (#sizeWordStack(WS) <Int #stackNeeded(OP)
              orBool   #sizeWordStack(WS) +Int #stackDelta(OP) >Int 1024)

   syntax Int ::= #stackNeeded ( OpCode ) [function]
// -------------------------------------------------
   rule #stackNeeded(PUSH(_, _))      => 0
   rule #stackNeeded(NOP:NullStackOp) => 0
   rule #stackNeeded(UOP:UnStackOp)   => 1
   rule #stackNeeded(BOP:BinStackOp)  => 2 requires notBool isLogOp(BOP)
   rule #stackNeeded(TOP:TernStackOp) => 3
   rule #stackNeeded(QOP:QuadStackOp) => 4
   rule #stackNeeded(DUP(N))          => N
   rule #stackNeeded(SWAP(N))         => N +Int 1
   rule #stackNeeded(LOG(N))          => N +Int 2
   rule #stackNeeded(DELEGATECALL)    => 6
   rule #stackNeeded(COP:CallOp)      => 7 requires COP =/=K DELEGATECALL

   syntax Int ::= #stackAdded ( OpCode ) [function]
// ------------------------------------------------
   rule #stackAdded(CALLDATACOPY) => 0
   rule #stackAdded(CODECOPY)     => 0
   rule #stackAdded(EXTCODECOPY)  => 0
   rule #stackAdded(POP)          => 0
   rule #stackAdded(MSTORE)       => 0
   rule #stackAdded(MSTORE8)      => 0
   rule #stackAdded(SSTORE)       => 0
   rule #stackAdded(JUMP)         => 0
   rule #stackAdded(JUMPI)        => 0
   rule #stackAdded(JUMPDEST)     => 0
   rule #stackAdded(STOP)         => 0
   rule #stackAdded(RETURN)       => 0
   rule #stackAdded(SELFDESTRUCT) => 0
   rule #stackAdded(PUSH(_,_))    => 1
   rule #stackAdded(LOG(_))       => 0
   rule #stackAdded(SWAP(N))      => N
   rule #stackAdded(DUP(N))       => N +Int 1
   rule #stackAdded(OP)           => 1 [owise]

   syntax Int ::= #stackDelta ( OpCode ) [function]
// ------------------------------------------------
   rule #stackDelta(OP) => #stackAdded(OP) -Int #stackNeeded(OP)
  • #badJumpDest? determines if the opcode will result in a bad jump destination.
   syntax InternalOp ::= "#badJumpDest?" "[" OpCode "]"
// ----------------------------------------------------
   rule <k> #badJumpDest? [ OP    ] => . ... </k> requires notBool isJumpOp(OP)
   rule <k> #badJumpDest? [ OP    ] => . ... </k> <wordStack> DEST  : WS </wordStack> <program> ... DEST |-> JUMPDEST ... </program> requires isJumpOp(OP)
   rule <k> #badJumpDest? [ JUMPI ] => . ... </k> <wordStack> _ : 0 : WS </wordStack>

   rule <k> #badJumpDest? [ JUMP  ] => #exception ... </k> <wordStack> DEST :     WS </wordStack> <program> ... DEST |-> OP ... </program> requires OP =/=K JUMPDEST
   rule <k> #badJumpDest? [ JUMPI ] => #exception ... </k> <wordStack> DEST : W : WS </wordStack> <program> ... DEST |-> OP ... </program> requires OP =/=K JUMPDEST andBool W =/=K 0

   rule <k> #badJumpDest? [ JUMP  ] => #exception ... </k> <wordStack> DEST :     WS </wordStack> <program> PGM </program> requires notBool (DEST in_keys(PGM))
   rule <k> #badJumpDest? [ JUMPI ] => #exception ... </k> <wordStack> DEST : W : WS </wordStack> <program> PGM </program> requires (notBool (DEST in_keys(PGM))) andBool W =/=K 0

Execution Step

  • #exec will load the arguments of the opcode (it assumes #stackNeeded? is accurate and has been called) and trigger the subsequent operations.
   syntax InternalOp ::= "#exec" "[" OpCode "]"
// --------------------------------------------
   rule <k> #exec [ OP ] => #gas [ OP ] ~> OP ... </k> requires isInternalOp(OP) orBool isNullStackOp(OP) orBool isPushOp(OP)

Here we load the correct number of arguments from the wordStack based on the sort of the opcode. Some of them require an argument to be interpereted as an address (modulo 160 bits), so the #addr? function performs that check.

   syntax InternalOp ::= UnStackOp Int
                       | BinStackOp Int Int
                       | TernStackOp Int Int Int
                       | QuadStackOp Int Int Int Int
// -------------------------------------------------
   rule <k> #exec [ UOP:UnStackOp   => UOP #addr?(UOP, W0)          ] ... </k> <wordStack> W0 : WS                => WS </wordStack>
   rule <k> #exec [ BOP:BinStackOp  => BOP #addr?(BOP, W0) W1       ] ... </k> <wordStack> W0 : W1 : WS           => WS </wordStack>
   rule <k> #exec [ TOP:TernStackOp => TOP #addr?(TOP, W0) W1 W2    ] ... </k> <wordStack> W0 : W1 : W2 : WS      => WS </wordStack>
   rule <k> #exec [ QOP:QuadStackOp => QOP #addr?(QOP, W0) W1 W2 W3 ] ... </k> <wordStack> W0 : W1 : W2 : W3 : WS => WS </wordStack>

   syntax Int ::= "#addr?" "(" OpCode "," Int ")" [function]
// ---------------------------------------------------------
   rule #addr?(BALANCE,      W) => #addr(W)
   rule #addr?(EXTCODESIZE,  W) => #addr(W)
   rule #addr?(EXTCODECOPY,  W) => #addr(W)
   rule #addr?(SELFDESTRUCT, W) => #addr(W)
   rule #addr?(OP, W)           => W        requires (OP =/=K BALANCE) andBool (OP =/=K EXTCODESIZE) andBool (OP =/=K EXTCODECOPY) andBool (OP =/=K SELFDESTRUCT)

StackOp is used for opcodes which require a large portion of the stack.

   syntax InternalOp ::= StackOp WordStack
// ---------------------------------------
   rule <k> #exec [ SO:StackOp => SO WS ] ... </k> <wordStack> WS </wordStack>

The CallOp opcodes all interperet their second argument as an address.

   syntax InternalOp ::= CallSixOp Int Int     Int Int Int Int
                       | CallOp    Int Int Int Int Int Int Int
// -----------------------------------------------------------
   rule <k> #exec [ CSO:CallSixOp => CSO W0 #addr(W1)    W2 W3 W4 W5 ] ... </k> <wordStack> W0 : W1 : W2 : W3 : W4 : W5 : WS      => WS </wordStack>
   rule <k> #exec [ CO:CallOp     => CO  W0 #addr(W1) W2 W3 W4 W5 W6 ] ... </k> <wordStack> W0 : W1 : W2 : W3 : W4 : W5 : W6 : WS => WS </wordStack>
  • #gas calculates how much gas this operation costs, and takes into account the memory consumed.
   syntax InternalOp ::= "#gas" "[" OpCode "]" | "#deductGas" | "#deductMemory"
// ----------------------------------------------------------------------------
   rule <k> #gas [ OP ] => #memory(OP, MU) ~> #deductMemory ~> #gasExec(SCHED, OP) ~> #deductGas ... </k> <memoryUsed> MU </memoryUsed> <schedule> SCHED </schedule>

   rule <k> MU':Int ~> #deductMemory => #exception ... </k> requires MU' >=Int pow256
   rule <k> MU':Int ~> #deductMemory => (Cmem(SCHED, MU') -Int Cmem(SCHED, MU)) ~> #deductGas ... </k>
        <memoryUsed> MU => MU' </memoryUsed> <schedule> SCHED </schedule>
     requires MU' <Int pow256

   rule <k> G:Int ~> #deductGas => #exception ... </k> <gas> GAVAIL                  </gas> requires GAVAIL <Int G
   rule <k> G:Int ~> #deductGas => .          ... </k> <gas> GAVAIL => GAVAIL -Int G </gas> <previousGas> _ => GAVAIL </previousGas> requires GAVAIL >=Int G

   syntax Int ::= Cmem ( Schedule , Int ) [function, memo]
// -------------------------------------------------------
   rule Cmem(SCHED, N) => (N *Int Gmemory < SCHED >) +Int ((N *Int N) /Int Gquadcoeff < SCHED >)

Program Counter

All operators except for PUSH and JUMP* increment the program counter by 1. The arguments to PUSH must be skipped over (as they are inline), and the opcode JUMP already affects the program counter in the correct way.

  • #pc calculates the next program counter of the given operator.
   syntax InternalOp ::= "#pc" "[" OpCode "]"
// ------------------------------------------
   rule <k> #pc [ OP         ] => . ... </k> <pc> PCOUNT => PCOUNT +Int 1          </pc> requires notBool (isPushOp(OP) orBool isJumpOp(OP))
   rule <k> #pc [ PUSH(N, _) ] => . ... </k> <pc> PCOUNT => PCOUNT +Int (1 +Int N) </pc>
   rule <k> #pc [ OP         ] => . ... </k> requires isJumpOp(OP)

   syntax Bool ::= isJumpOp ( OpCode ) [function]
// ----------------------------------------------
   rule isJumpOp(OP) => OP ==K JUMP orBool OP ==K JUMPI

Substate Log

During execution of a transaction some things are recorded in the substate log (Section 6.1 in yellowpaper). This is a right cons-list of SubstateLogEntry (which contains the account ID along with the specified portions of the wordStack and localMem).

   syntax SubstateLogEntry ::= "{" Int "|" WordStack "|" WordStack "}"
// -------------------------------------------------------------------

After executing a transaction, it’s necessary to have the effect of the substate log recorded.

  • #finalizeTx makes the substate log actually have an effect on the state.
   syntax InternalOp ::= #finalizeTx ( Bool )
// ------------------------------------------
   rule <k> #finalizeTx(true) => . ... </k>
        <selfDestruct> .Set </selfDestruct>

   rule <k> #finalizeTx(false => true) ... </k>
        <mode> VMTESTS </mode>
        <id> ACCT </id>
        <refund> BAL => 0 </refund>
        <account>
          <acctID> ACCT </acctID>
          <balance> CURRBAL => CURRBAL +Word BAL </balance>
          ...
        </account>
        <activeAccounts> ... ACCT |-> (EMPTY => #if BAL >Int 0 #then false #else EMPTY #fi) ... </activeAccounts>

   rule <k> (.K => #newAccount MINER) ~> #finalizeTx(_)... </k>
        <mode> NORMAL </mode>
        <coinbase> MINER </coinbase>
        <activeAccounts> ACCTS </activeAccounts>
     requires notBool MINER in_keys(ACCTS)

   rule <k> #finalizeTx(false) ... </k>
        <mode> NORMAL </mode>
        <gas> GAVAIL => G*(GAVAIL, GLIMIT, REFUND) </gas>
        <refund> REFUND => 0 </refund>
        <txPending> ListItem(MSGID:Int) ... </txPending>
        <message>
           <msgID> MSGID </msgID>
           <txGasLimit> GLIMIT </txGasLimit>
           ...
        </message>
     requires REFUND =/=Int 0

   rule <k> #finalizeTx(false => true) ... </k>
        <mode> NORMAL </mode>
        <origin> ORG </origin>
        <coinbase> MINER </coinbase>
        <gas> GAVAIL </gas>
        <refund> 0 </refund>
        <account>
          <acctID> ORG </acctID>
          <balance> ORGBAL => ORGBAL +Int GAVAIL *Int GPRICE </balance>
          ...
        </account>
        <account>
          <acctID> MINER </acctID>
          <balance> MINBAL => MINBAL +Int (GLIMIT -Int GAVAIL) *Int GPRICE </balance>
          ...
        </account>
        <txPending> ListItem(TXID:Int) => .List ... </txPending>
        <message>
          <msgID> TXID </msgID>
          <txGasLimit> GLIMIT </txGasLimit>
          <txGasPrice> GPRICE </txGasPrice>
          ...
        </message>
        <activeAccounts> ... ORG |-> (ORGEMPTY => #if GAVAIL *Int GPRICE >Int 0 #then false #else ORGEMPTY #fi) MINER |-> (MINEMPTY => #if (GLIMIT -Int GAVAIL) *Int GPRICE >Int 0 #then false #else MINEMPTY #fi) ... </activeAccounts>
     requires ORG =/=Int MINER

   rule <k> #finalizeTx(false => true) ... </k>
        <mode> NORMAL </mode>
        <origin> ACCT </origin>
        <coinbase> ACCT </coinbase>
        <refund> 0 </refund>
        <account>
          <acctID> ACCT </acctID>
          <balance> BAL => BAL +Int GLIMIT *Int GPRICE </balance>
          ...
        </account>
        <txPending> ListItem(MsgId:Int) => .List ... </txPending>
        <message>
          <msgID> MsgId </msgID>
          <txGasLimit> GLIMIT </txGasLimit>
          <txGasPrice> GPRICE </txGasPrice>
          ...
        </message>
        <activeAccounts> ... ACCT |-> (EMPTY => #if GLIMIT *Int GPRICE >Int 0 #then false #else EMPTY #fi) ... </activeAccounts>

   rule <k> #finalizeTx(true) ... </k>
        <selfDestruct> ... (SetItem(ACCT) => .Set) </selfDestruct>
        <activeAccounts> ... (ACCT |-> _ => .Map) </activeAccounts>
        <accounts>
          ( <account>
              <acctID> ACCT </acctID>
              ...
            </account>
         => .Bag
          )
          ...
        </accounts>

EVM Programs

Lists of opcodes form programs. Deciding if an opcode is in a list will be useful for modeling gas, and converting a program into a map of program-counter to opcode is useful for execution.

Note that _in_ ignores the arguments to operators that are parametric.

   syntax OpCodes ::= ".OpCodes" | OpCode ";" OpCodes
// --------------------------------------------------

   syntax Map ::= #asMapOpCodes ( OpCodes )             [function]
                | #asMapOpCodes ( Int , OpCodes , Map ) [function, klabel(#asMapOpCodesAux)]
// -----------------------------------------------------------------------------------------
   rule #asMapOpCodes( OPS::OpCodes ) => #asMapOpCodes(0, OPS, .Map)

   rule #asMapOpCodes( N , .OpCodes         , MAP ) => MAP
   rule #asMapOpCodes( N , OP:OpCode  ; OCS , MAP ) => #asMapOpCodes(N +Int 1, OCS, MAP (N |-> OP)) requires notBool isPushOp(OP)
   rule #asMapOpCodes( N , PUSH(M, W) ; OCS , MAP ) => #asMapOpCodes(N +Int 1 +Int M, OCS, MAP (N |-> PUSH(M, W)))

   syntax OpCodes ::= #asOpCodes ( Map )                 [function]
                    | #asOpCodes ( Int , Map , OpCodes ) [function, klabel(#asOpCodesAux)]
// ---------------------------------------------------------------------------------------
   rule #asOpCodes(M) => #asOpCodes(0, M, .OpCodes)

   rule #asOpCodes(N, .Map,               OPS) => OPS
   rule #asOpCodes(N, N |-> OP         M, OPS) => #asOpCodes(N +Int 1,        M, OP         ; OPS) requires notBool isPushOp(OP)
   rule #asOpCodes(N, N |-> PUSH(S, W) M, OPS) => #asOpCodes(N +Int 1 +Int S, M, PUSH(S, W) ; OPS)

EVM OpCodes

Each subsection has a different class of opcodes. Organization is based roughly on what parts of the execution state are needed to compute the result of each operator. This sometimes corresponds to the organization in the yellowpaper.

Internal Operations

These are just used by the other operators for shuffling local execution state around on the EVM.

  • #push will push an element to the wordStack without any checks.
  • #setStack_ will set the current stack to the given one.
   syntax InternalOp ::= "#push" | "#setStack" WordStack
// -----------------------------------------------------
   rule <k> W0:Int ~> #push => . ... </k> <wordStack> WS => W0 : WS </wordStack>
   rule <k> #setStack WS    => . ... </k> <wordStack> _  => WS      </wordStack>
  • #newAccount_ allows declaring a new empty account with the given address (and assumes the rounding to 160 bits has already occured). If the account already exists with non-zero nonce or non-empty code, an exception is thrown. Otherwise, if the account already exists, the storage is cleared.
   syntax InternalOp ::= "#newAccount" Int
// ---------------------------------------
   rule <k> #newAccount ACCT => #exception ... </k>
        <account>
          <acctID> ACCT  </acctID>
          <code>   CODE  </code>
          <nonce>  NONCE </nonce>
          ...
        </account>
     requires CODE =/=K .WordStack orBool NONCE =/=K 0

   rule <k> #newAccount ACCT => . ... </k>
        <account>
          <acctID>  ACCT       </acctID>
          <code>    .WordStack </code>
          <nonce>   0          </nonce>
          <storage> _ => .Map  </storage>
          ...
        </account>

   rule <k> #newAccount ACCT => . ... </k>
        <activeAccounts> ACCTS (.Map => ACCT |-> true) </activeAccounts>
        <accounts>
          ( .Bag
         => <account>
              <acctID>  ACCT       </acctID>
              <balance> 0          </balance>
              <code>    .WordStack </code>
              <storage> .Map       </storage>
              <nonce>   0          </nonce>
            </account>
          )
          ...
        </accounts>
     requires notBool ACCT in_keys(ACCTS)
  • #transferFunds moves money from one account into another, creating the destination account if it doesn’t exist.
   syntax InternalOp ::= "#transferFunds" Int Int Int
// --------------------------------------------------
   rule <k> #transferFunds ACCTFROM ACCTTO VALUE => . ... </k>
        <account>
          <acctID> ACCTFROM </acctID>
          <balance> ORIGFROM => ORIGFROM -Word VALUE </balance>
          <nonce> NONCE </nonce>
          <code> CODE </code>
          ...
        </account>
        <account>
          <acctID> ACCTTO </acctID>
          <balance> ORIGTO => ORIGTO +Word VALUE </balance>
          ...
        </account>
        <activeAccounts> ... ACCTTO |-> (EMPTY => #if VALUE >Int 0 #then false #else EMPTY #fi) ACCTFROM |-> (_ => ORIGFROM ==Int VALUE andBool NONCE ==Int 0 andBool CODE ==K .WordStack) ... </activeAccounts>
     requires ACCTFROM =/=K ACCTTO andBool VALUE <=Int ORIGFROM

   rule <k> #transferFunds ACCTFROM ACCTTO VALUE => #exception ... </k>
        <account>
          <acctID> ACCTFROM </acctID>
          <balance> ORIGFROM </balance>
          ...
        </account>
     requires VALUE >Int ORIGFROM

   rule <k> (. => #newAccount ACCTTO) ~> #transferFunds ACCTFROM ACCTTO VALUE ... </k>
        <activeAccounts> ACCTS </activeAccounts>
     requires ACCTFROM =/=K ACCTTO andBool notBool ACCTTO in_keys(ACCTS)

   rule <k> #transferFunds ACCT ACCT VALUE => . ... </k>
        <account>
          <acctID> ACCT </acctID>
          <balance> ORIGFROM </balance>
          ...
        </account>
     requires VALUE <=Int ORIGFROM

Invalid Operator

We use INVALID both for marking the designated invalid operator and for garbage bytes in the input program.

   syntax InvalidOp ::= "INVALID"
// ------------------------------

Stack Manipulations

Some operators don’t calculate anything, they just push the stack around a bit.

   syntax UnStackOp ::= "POP"
// --------------------------
   rule <k> POP W => . ... </k>

   syntax StackOp ::= DUP ( Int ) | SWAP ( Int )
// ---------------------------------------------
   rule <k> DUP(N)  WS:WordStack => #setStack ((WS [ N -Int 1 ]) : WS)                      ... </k>
   rule <k> SWAP(N) (W0 : WS)    => #setStack ((WS [ N -Int 1 ]) : (WS [ N -Int 1 := W0 ])) ... </k>

   syntax PushOp ::= PUSH ( Int , Int )
// ------------------------------------
   rule <k> PUSH(_, W) => W ~> #push ... </k>

Local Memory

These operations are getters/setters of the local execution memory.

   syntax UnStackOp ::= "MLOAD"
// ----------------------------
   rule <k> MLOAD INDEX => #asWord(#range(LM, INDEX, 32)) ~> #push ... </k>
        <localMem> LM </localMem>

   syntax BinStackOp ::= "MSTORE" | "MSTORE8"
// ------------------------------------------
   rule <k> MSTORE INDEX VALUE => . ... </k>
        <localMem> LM => LM [ INDEX := #padToWidth(32, #asByteStack(VALUE)) ] </localMem>

   rule <k> MSTORE8 INDEX VALUE => . ... </k>
        <localMem> LM => LM [ INDEX <- (VALUE %Int 256) ]    </localMem>

Expressions

Expression calculations are simple and don’t require anything but the arguments from the wordStack to operate.

NOTE: We have to call the opcode OR by EVMOR instead, because K has trouble parsing it/compiling the definition otherwise.

   syntax UnStackOp ::= "ISZERO" | "NOT"
// -------------------------------------
   rule <k> ISZERO 0 => 1        ~> #push ... </k>
   rule <k> ISZERO W => 0        ~> #push ... </k> requires W =/=K 0
   rule <k> NOT    W => ~Word W  ~> #push ... </k>

   syntax BinStackOp ::= "ADD" | "MUL" | "SUB" | "DIV" | "EXP" | "MOD"
// -------------------------------------------------------------------
   rule <k> ADD W0 W1 => W0 +Word W1 ~> #push ... </k>
   rule <k> MUL W0 W1 => W0 *Word W1 ~> #push ... </k>
   rule <k> SUB W0 W1 => W0 -Word W1 ~> #push ... </k>
   rule <k> DIV W0 W1 => W0 /Word W1 ~> #push ... </k>
   rule <k> EXP W0 W1 => W0 ^Word W1 ~> #push ... </k>
   rule <k> MOD W0 W1 => W0 %Word W1 ~> #push ... </k>

   syntax BinStackOp ::= "SDIV" | "SMOD"
// -------------------------------------
   rule <k> SDIV W0 W1 => W0 /sWord W1 ~> #push ... </k>
   rule <k> SMOD W0 W1 => W0 %sWord W1 ~> #push ... </k>

   syntax TernStackOp ::= "ADDMOD" | "MULMOD"
// ------------------------------------------
   rule <k> ADDMOD W0 W1 W2 => (W0 +Int W1) %Word W2 ~> #push ... </k>
   rule <k> MULMOD W0 W1 W2 => (W0 *Int W1) %Word W2 ~> #push ... </k>

   syntax BinStackOp ::= "BYTE" | "SIGNEXTEND"
// -------------------------------------------
   rule <k> BYTE INDEX W     => byte(INDEX, W)     ~> #push ... </k>
   rule <k> SIGNEXTEND W0 W1 => signextend(W0, W1) ~> #push ... </k>

   syntax BinStackOp ::= "AND" | "EVMOR" | "XOR"
// ---------------------------------------------
   rule <k> AND   W0 W1 => W0 &Word W1   ~> #push ... </k>
   rule <k> EVMOR W0 W1 => W0 |Word W1   ~> #push ... </k>
   rule <k> XOR   W0 W1 => W0 xorWord W1 ~> #push ... </k>

   syntax BinStackOp ::= "LT" | "GT" | "EQ"
// ----------------------------------------
   rule <k> LT W0 W1 => 1 ~> #push ... </k>  requires W0 <Int   W1
   rule <k> LT W0 W1 => 0 ~> #push ... </k>  requires W0 >=Int  W1
   rule <k> GT W0 W1 => 1 ~> #push ... </k>  requires W0 >Int   W1
   rule <k> GT W0 W1 => 0 ~> #push ... </k>  requires W0 <=Int  W1
   rule <k> EQ W0 W1 => 1 ~> #push ... </k>  requires W0 ==Int  W1
   rule <k> EQ W0 W1 => 0 ~> #push ... </k>  requires W0 =/=Int W1

   syntax BinStackOp ::= "SLT" | "SGT"
// -----------------------------------
   rule <k> SLT W0 W1 => W0 s<Word W1 ~> #push ... </k>
   rule <k> SGT W0 W1 => W1 s<Word W0 ~> #push ... </k>

   syntax BinStackOp ::= "SHA3"
// ----------------------------
   rule <k> SHA3 MEMSTART MEMWIDTH => keccak(#range(LM, MEMSTART, MEMWIDTH)) ~> #push ... </k>
        <localMem> LM </localMem>

Local State

These operators make queries about the current execution state.

   syntax NullStackOp ::= "PC" | "GAS" | "GASPRICE" | "GASLIMIT"
// -------------------------------------------------------------
   rule <k> PC       => PCOUNT ~> #push ... </k> <pc> PCOUNT </pc>
   rule <k> GAS      => GAVAIL ~> #push ... </k> <gas> GAVAIL </gas>
   rule <k> GASPRICE => GPRICE ~> #push ... </k> <gasPrice> GPRICE </gasPrice>
   rule <k> GASLIMIT => GLIMIT ~> #push ... </k> <gasLimit> GLIMIT </gasLimit>

   syntax NullStackOp ::= "COINBASE" | "TIMESTAMP" | "NUMBER" | "DIFFICULTY"
// -------------------------------------------------------------------------
   rule <k> COINBASE   => CB   ~> #push ... </k> <coinbase> CB </coinbase>
   rule <k> TIMESTAMP  => TS   ~> #push ... </k> <timestamp> TS </timestamp>
   rule <k> NUMBER     => NUMB ~> #push ... </k> <number> NUMB </number>
   rule <k> DIFFICULTY => DIFF ~> #push ... </k> <difficulty> DIFF </difficulty>

   syntax NullStackOp ::= "ADDRESS" | "ORIGIN" | "CALLER" | "CALLVALUE"
// --------------------------------------------------------------------
   rule <k> ADDRESS   => ACCT ~> #push ... </k> <id> ACCT </id>
   rule <k> ORIGIN    => ORG  ~> #push ... </k> <origin> ORG </origin>
   rule <k> CALLER    => CL   ~> #push ... </k> <caller> CL </caller>
   rule <k> CALLVALUE => CV   ~> #push ... </k> <callValue> CV </callValue>

   syntax NullStackOp ::= "MSIZE" | "CODESIZE"
// -------------------------------------------
   rule <k> MSIZE    => 32 *Word MU         ~> #push ... </k> <memoryUsed> MU </memoryUsed>
   rule <k> CODESIZE => #sizeWordStack(PGM) ~> #push ... </k> <programBytes> PGM </programBytes>

   syntax TernStackOp ::= "CODECOPY"
// ---------------------------------
   rule <k> CODECOPY MEMSTART PGMSTART WIDTH => . ... </k>
        <programBytes> PGM </programBytes>
        <localMem> LM => LM [ MEMSTART := PGM [ PGMSTART .. WIDTH ] ] </localMem>

   syntax UnStackOp ::= "BLOCKHASH"
// --------------------------------
   rule <k> BLOCKHASH N => #if N >=Int HI orBool HI -Int 256 >Int N #then 0 #else #parseHexWord(Keccak256(Int2String(N))) #fi ~> #push ... </k> <number> HI </number> <mode> VMTESTS </mode>

   rule <k> BLOCKHASH N => #blockhash(HASHES, N, HI -Int 1, 0) ~> #push ... </k> <number> HI </number> <blockhash> HASHES </blockhash> <mode> NORMAL </mode>

   syntax Int ::= #blockhash ( List , Int , Int , Int ) [function]
// ---------------------------------------------------------------
   rule #blockhash(_, N, HI, _) => 0 requires N >Int HI
   rule #blockhash(_, _, _, 256) => 0
   rule #blockhash(ListItem(0) _, _, _, _) => 0
   rule #blockhash(ListItem(H) _, N, N, _) => H
   rule #blockhash(ListItem(_) L, N, HI, A) => #blockhash(L, N, HI -Int 1, A +Int 1) [owise]

JUMP*

The JUMP* family of operations affect the current program counter.

   syntax NullStackOp ::= "JUMPDEST"
// ---------------------------------
   rule <k> JUMPDEST => . ... </k>

   syntax UnStackOp ::= "JUMP"
// ---------------------------
   rule <k> JUMP DEST => . ... </k> <pc> _ => DEST </pc>

   syntax BinStackOp ::= "JUMPI"
// -----------------------------
   rule <k> JUMPI DEST I => . ... </k> <pc> _      => DEST          </pc> requires I =/=K 0
   rule <k> JUMPI DEST 0 => . ... </k> <pc> PCOUNT => PCOUNT +Int 1 </pc>

STOP and RETURN

   syntax NullStackOp ::= "STOP"
// -----------------------------
   rule <k> STOP => #end ... </k>

   syntax BinStackOp ::= "RETURN"
// ------------------------------
   rule <mode> EXECMODE </mode>
        <k> RETURN RETSTART RETWIDTH => #end ... </k>
        <output> _ => #range(LM, RETSTART, RETWIDTH) </output>
        <localMem> LM </localMem>

Call Data

These operators query about the current CALL* state.

   syntax NullStackOp ::= "CALLDATASIZE"
// -------------------------------------
   rule <k> CALLDATASIZE => #sizeWordStack(CD) ~> #push ... </k>
        <callData> CD </callData>

   syntax UnStackOp ::= "CALLDATALOAD"
// -----------------------------------
   rule <k> CALLDATALOAD DATASTART => #asWord(CD [ DATASTART .. 32 ]) ~> #push ... </k>
        <callData> CD </callData>

   syntax TernStackOp ::= "CALLDATACOPY"
// -------------------------------------
   rule <k> CALLDATACOPY MEMSTART DATASTART DATAWIDTH => . ... </k>
        <localMem> LM => LM [ MEMSTART := CD [ DATASTART .. DATAWIDTH ] ] </localMem>
        <callData> CD </callData>

Log Operations

   syntax BinStackOp ::= LogOp
   syntax LogOp ::= LOG ( Int )
// ----------------------------
   rule <k> LOG(N) MEMSTART MEMWIDTH => . ... </k>
        <id> ACCT </id>
        <wordStack> WS => #drop(N, WS) </wordStack>
        <localMem> LM </localMem>
        <log> ... (.List => ListItem({ ACCT | #take(N, WS) | #range(LM, MEMSTART, MEMWIDTH) })) </log>
     requires #sizeWordStack(WS) >=Int N

Ethereum Network OpCodes

Operators that require access to the rest of the Ethereum network world-state can be taken as a first draft of a “blockchain generic” language.

Account Queries

TODO: It’s unclear what to do in the case of an account not existing for these operators. BALANCE is specified to push 0 in this case, but the others are not specified. For now, I assume that they instantiate an empty account and use the empty data.

   syntax UnStackOp ::= "BALANCE"
// ------------------------------
   rule <k> BALANCE ACCT => BAL ~> #push ... </k>
        <account>
          <acctID> ACCT </acctID>
          <balance> BAL </balance>
          ...
        </account>

   rule <k> BALANCE ACCT => #newAccount ACCT ~> 0 ~> #push ... </k>
        <activeAccounts> ACCTS </activeAccounts>
     requires notBool ACCT in_keys(ACCTS)

   syntax UnStackOp ::= "EXTCODESIZE"
// ----------------------------------
   rule <k> EXTCODESIZE ACCT => #sizeWordStack(CODE) ~> #push ... </k>
        <account>
          <acctID> ACCT </acctID>
          <code> CODE </code>
          ...
        </account>

   rule <k> EXTCODESIZE ACCT => #newAccount ACCT ~> 0 ~> #push ... </k>
        <activeAccounts> ACCTS </activeAccounts>
     requires notBool ACCT in_keys(ACCTS)

TODO: What should happen in the case that the account doesn’t exist with EXTCODECOPY? Should we pad zeros (for the copied “program”)?

   syntax QuadStackOp ::= "EXTCODECOPY"
// ------------------------------------
   rule <k> EXTCODECOPY ACCT MEMSTART PGMSTART WIDTH => . ... </k>
        <localMem> LM => LM [ MEMSTART := PGM [ PGMSTART .. WIDTH ] ] </localMem>
        <account>
          <acctID> ACCT </acctID>
          <code> PGM </code>
          ...
        </account>

   rule <k> EXTCODECOPY ACCT MEMSTART PGMSTART WIDTH => #newAccount ACCT ... </k>
        <activeAccounts> ACCTS </activeAccounts>
     requires notBool ACCT in_keys(ACCTS)

Account Storage Operations

These operations interact with the account storage.

   syntax UnStackOp ::= "SLOAD"
// ----------------------------
   rule <k> SLOAD INDEX => 0 ~> #push ... </k>
        <id> ACCT </id>
        <account>
          <acctID> ACCT </acctID>
          <storage> STORAGE </storage>
          ...
        </account> requires notBool INDEX in_keys(STORAGE)

   rule <k> SLOAD INDEX => VALUE ~> #push ... </k>
        <id> ACCT </id>
        <account>
          <acctID> ACCT </acctID>
          <storage> ... INDEX |-> VALUE ... </storage>
          ...
        </account>

   syntax BinStackOp ::= "SSTORE"
// ------------------------------
   rule <k> SSTORE INDEX VALUE => . ... </k>
        <id> ACCT </id>
        <account>
          <acctID> ACCT </acctID>
          <storage> ... (INDEX |-> (OLD => VALUE)) ... </storage>
          ...
        </account>
        <refund> R => #ifInt OLD =/=Int 0 andBool VALUE ==Int 0
                       #then R +Word Rsstoreclear < SCHED >
                       #else R
                      #fi
        </refund>
        <schedule> SCHED </schedule>

   rule <k> SSTORE INDEX VALUE => . ... </k>
        <id> ACCT </id>
        <account>
          <acctID> ACCT </acctID>
          <storage> STORAGE => STORAGE [ INDEX <- VALUE ] </storage>
          ...
        </account>
     requires notBool (INDEX in_keys(STORAGE))

Call Operations

The various CALL* (and other inter-contract control flow) operations will be desugared into these InternalOps.

  • The callLog is used to store the CALL*/CREATE operations so that we can compare them against the test-set.
   syntax Call ::= "{" Int "|" Int "|" Int "|" WordStack "}"
// ---------------------------------------------------------
  • #call_____ takes the calling account, the account to execute as, the account whose code should execute, the gas limit, the amount to transfer, and the arguments.
  • #callWithCode______ takes the calling account, the accout to execute as, the code to execute (as a map), the gas limit, the amount to transfer, and the arguments.
  • #return__ is a placeholder for the calling program, specifying where to place the returned data in memory.
   syntax InternalOp ::= "#checkCall" Int Int
                       | "#call" Int Int Int Int Int Int WordStack
                       | "#callWithCode" Int Int Map WordStack Int Int Int WordStack
                       | "#mkCall" Int Int Map WordStack Int Int Int WordStack
// ---------------------------------------------------------------------------
   rule <k> #checkCall ACCT VALUE ~> #call _ _ _ GLIMIT _ _ _ => #refund GLIMIT ~> #pushCallStack ~> #pushWorldState ~> #pushSubstate ~> #exception ... </k>
        <callDepth> CD </callDepth>
        <account>
          <acctID> ACCT </acctID>
          <balance> BAL </balance>
          ...
        </account>
     requires VALUE >Int BAL orBool CD >=Int 1024

    rule <k> #checkCall ACCT VALUE => . ... </k>
        <callDepth> CD </callDepth>
        <account>
          <acctID> ACCT </acctID>
          <balance> BAL </balance>
          ...
        </account>
     requires notBool (VALUE >Int BAL orBool CD >=Int 1024)

   rule <k> #call ACCTFROM ACCTTO ACCTCODE GLIMIT VALUE APPVALUE ARGS
         => #callWithCode ACCTFROM ACCTTO (0 |-> #precompiled(ACCTCODE)) .WordStack GLIMIT VALUE APPVALUE ARGS
        ...
        </k>
     requires ACCTCODE >Int 0 andBool ACCTCODE <=Int 4

   rule <k> #call ACCTFROM ACCTTO ACCTCODE GLIMIT VALUE APPVALUE ARGS
         => #callWithCode ACCTFROM ACCTTO #asMapOpCodes(#dasmOpCodes(CODE, SCHED)) CODE GLIMIT VALUE APPVALUE ARGS
        ...
        </k>
        <schedule> SCHED </schedule>
        <acctID> ACCTCODE </acctID>
        <code> CODE </code>
     requires notBool (ACCTCODE >Int 0 andBool ACCTCODE <=Int 4)

   rule <k> #call ACCTFROM ACCTTO ACCTCODE GLIMIT VALUE APPVALUE ARGS
         => #callWithCode ACCTFROM ACCTTO .Map .WordStack GLIMIT VALUE APPVALUE ARGS
        ...
        </k>
        <activeAccounts> ACCTS </activeAccounts>
     requires notBool (ACCTCODE >Int 0 andBool ACCTCODE <=Int 4) andBool notBool ACCTCODE in_keys(ACCTS)

   rule #callWithCode ACCTFROM ACCTTO CODE BYTES GLIMIT VALUE APPVALUE ARGS
     => #pushCallStack ~> #pushWorldState ~> #pushSubstate
     ~> #transferFunds ACCTFROM ACCTTO VALUE
     ~> #mkCall ACCTFROM ACCTTO CODE BYTES GLIMIT VALUE APPVALUE ARGS

   rule <mode> EXECMODE </mode>
        <k> #mkCall ACCTFROM ACCTTO CODE BYTES GLIMIT VALUE APPVALUE ARGS
         => #initVM ~> #if EXECMODE ==K VMTESTS #then #end #else #execute #fi
        ...
        </k>
        <callLog> ... (.Set => #ifSet EXECMODE ==K VMTESTS #then SetItem({ ACCTTO | GLIMIT | VALUE | ARGS }) #else .Set #fi) </callLog>
        <callDepth> CD => CD +Int 1 </callDepth>
        <callData> _ => ARGS </callData>
        <callValue> _ => APPVALUE </callValue>
        <id> _ => ACCTTO </id>
        <gas> _ => GLIMIT </gas>
        <caller> _ => ACCTFROM </caller>
        <program> _ => CODE </program>
        <programBytes> _ => BYTES </programBytes>

   syntax KItem ::= "#initVM"
// --------------------------
   rule <k> #initVM    => . ...      </k>
        <pc>         _ => 0          </pc>
        <memoryUsed> _ => 0          </memoryUsed>
        <output>     _ => .WordStack </output>
        <wordStack>  _ => .WordStack </wordStack>
        <localMem>   _ => .Map       </localMem>

   syntax KItem ::= "#return" Int Int
// ----------------------------------
   rule <k> #exception ~> #return _ _
         => #popCallStack ~> #popWorldState ~> #popSubstate ~> 0 ~> #push
        ...
        </k>

   rule <mode> EXECMODE </mode>
        <k> #end ~> #return RETSTART RETWIDTH
         => #popCallStack
         ~> #if EXECMODE ==K VMTESTS #then #popWorldState #else #dropWorldState #fi
         ~> #dropSubstate
         ~> 1 ~> #push ~> #refund GAVAIL ~> #setLocalMem RETSTART RETWIDTH OUT
        ...
        </k>
        <output> OUT </output>
        <gas> GAVAIL </gas>

   syntax InternalOp ::= "#refund" Int
                       | "#setLocalMem" Int Int WordStack
// ------------------------------------------------------
   rule <k> #refund G => . ... </k> <gas> GAVAIL => GAVAIL +Int G </gas>

   rule <k> #setLocalMem START WIDTH WS => . ... </k>
        <localMem> LM => LM [ START := #take(minInt(WIDTH, #sizeWordStack(WS)), WS) ] </localMem>

For each CALL* operation, we make a corresponding call to #call and a state-change to setup the custom parts of the calling environment.

   syntax CallOp ::= "CALL"
// ------------------------
   rule <k> CALL GCAP ACCTTO VALUE ARGSTART ARGWIDTH RETSTART RETWIDTH
         => #checkCall ACCTFROM VALUE
         ~> #call ACCTFROM ACCTTO ACCTTO Ccallgas(SCHED, ACCTTO, ACCTS, GCAP, GAVAIL, VALUE) VALUE VALUE #range(LM, ARGSTART, ARGWIDTH)
         ~> #return RETSTART RETWIDTH
        ...
        </k>
        <schedule> SCHED </schedule>
        <id> ACCTFROM </id>
        <localMem> LM </localMem>
        <activeAccounts> ACCTS </activeAccounts>
        <previousGas> GAVAIL </previousGas>

   syntax CallOp ::= "CALLCODE"
// ----------------------------
   rule <k> CALLCODE GCAP ACCTTO VALUE ARGSTART ARGWIDTH RETSTART RETWIDTH
         => #checkCall ACCTFROM VALUE
         ~> #call ACCTFROM ACCTFROM ACCTTO Ccallgas(SCHED, ACCTFROM, ACCTS, GCAP, GAVAIL, VALUE) VALUE VALUE #range(LM, ARGSTART, ARGWIDTH)
         ~> #return RETSTART RETWIDTH
        ...
        </k>
        <schedule> SCHED </schedule>
        <id> ACCTFROM </id>
        <localMem> LM </localMem>
        <activeAccounts> ACCTS </activeAccounts>
        <previousGas> GAVAIL </previousGas>

   syntax CallSixOp ::= "DELEGATECALL"
// -----------------------------------
   rule <k> DELEGATECALL GCAP ACCTTO ARGSTART ARGWIDTH RETSTART RETWIDTH
         => #checkCall ACCTFROM 0
         ~> #call ACCTAPPFROM ACCTFROM ACCTTO Ccallgas(SCHED, ACCTFROM, ACCTS, GCAP, GAVAIL, 0) 0 VALUE #range(LM, ARGSTART, ARGWIDTH)
         ~> #return RETSTART RETWIDTH
        ...
        </k>
        <schedule> SCHED </schedule>
        <id> ACCTFROM </id>
        <caller> ACCTAPPFROM </caller>
        <callValue> VALUE </callValue>
        <localMem> LM </localMem>
        <activeAccounts> ACCTS </activeAccounts>
        <previousGas> GAVAIL </previousGas>

Account Creation/Deletion

  • #create____ transfers the endowment to the new account and triggers the execution of the initialization code.
  • #codeDeposit_ checks the result of initialization code and whether the code deposit can be paid, indicating an error if not.
   syntax InternalOp ::= "#create" Int Int Int Int WordStack
                       | "#mkCreate" Int Int WordStack Int Int
                       | "#checkCreate" Int Int
// --------------------------------------------
   rule <k> #checkCreate ACCT VALUE ~> #create _ _ GAVAIL _ _ => #refund GAVAIL ~> #pushCallStack ~> #pushWorldState ~> #pushSubstate ~> #exception ... </k>
        <callDepth> CD </callDepth>
        <account>
          <acctID> ACCT </acctID>
          <balance> BAL </balance>
          ...
        </account>
     requires VALUE >Int BAL orBool CD >=Int 1024

   rule <k> #checkCreate ACCT VALUE => . ... </k>
        <mode> EXECMODE </mode>
        <callDepth> CD </callDepth>
        <account>
          <acctID> ACCT </acctID>
          <balance> BAL </balance>
          <nonce> NONCE => #ifInt EXECMODE ==K VMTESTS #then NONCE #else NONCE +Int 1 #fi </nonce>
          ...
        </account>
        <activeAccounts> ... ACCT |-> (EMPTY => #if EXECMODE ==K VMTESTS #then EMPTY #else false #fi) ... </activeAccounts>
     requires notBool (VALUE >Int BAL orBool CD >=Int 1024)

   rule #create ACCTFROM ACCTTO GAVAIL VALUE INITCODE
     => #pushCallStack ~> #pushWorldState ~> #pushSubstate
     ~> #newAccount ACCTTO
     ~> #transferFunds ACCTFROM ACCTTO VALUE
     ~> #mkCreate ACCTFROM ACCTTO INITCODE GAVAIL VALUE

   rule <mode> EXECMODE </mode>
        <k> #mkCreate ACCTFROM ACCTTO INITCODE GAVAIL VALUE
         => #initVM ~> #if EXECMODE ==K VMTESTS #then #end #else #execute #fi
        ...
        </k>
        <schedule> SCHED </schedule>
        <id> ACCT => ACCTTO </id>
        <gas> OLDGAVAIL => GAVAIL </gas>
        <program> _ => #asMapOpCodes(#dasmOpCodes(INITCODE, SCHED)) </program>
        <programBytes> _ => INITCODE </programBytes>
        <caller> _ => ACCTFROM </caller>
        <callLog> ... (.Set => #ifSet EXECMODE ==K VMTESTS #then SetItem({ 0 | OLDGAVAIL +Int GAVAIL | VALUE | INITCODE }) #else .Set #fi) </callLog>
        <callDepth> CD => CD +Int 1 </callDepth>
        <callData> _ => .WordStack </callData>
        <callValue> _ => VALUE </callValue>
        <account>
          <acctID> ACCTTO </acctID>
          <nonce> NONCE => #ifInt Gemptyisnonexistent << SCHED >> #then NONCE +Int 1 #else NONCE #fi </nonce>
          ...
        </account>
        <activeAccounts> ... ACCTTO |-> (EMPTY => #if Gemptyisnonexistent << SCHED >> #then false #else EMPTY #fi) ... </activeAccounts>

   syntax KItem ::= "#codeDeposit" Int
                  | "#mkCodeDeposit" Int
                  | "#finishCodeDeposit" Int WordStack
// ---------------------------------------------------
   rule <k> #exception ~> #codeDeposit _ => #popCallStack ~> #popWorldState ~> #popSubstate ~> 0 ~> #push ... </k>

   rule <mode> EXECMODE </mode>
        <k> #end ~> #codeDeposit ACCT => #mkCodeDeposit ACCT ... </k>

   rule <k> #mkCodeDeposit ACCT
         => #if EXECMODE ==K VMTESTS #then . #else Gcodedeposit < SCHED > *Int #sizeWordStack(OUT) ~> #deductGas #fi
         ~> #finishCodeDeposit ACCT OUT
        ...
        </k>
        <mode> EXECMODE </mode>
        <schedule> SCHED </schedule>
        <output> OUT => .WordStack </output>
     requires #sizeWordStack(OUT) <=Int maxCodeSize < SCHED >

   rule <k> #mkCodeDeposit ACCT => #popCallStack ~> #popWorldState ~> #popSubstate ~> 0 ~> #push ... </k>
        <schedule> SCHED </schedule>
        <output> OUT => .WordStack </output>
     requires #sizeWordStack(OUT) >Int maxCodeSize < SCHED >

   rule <k> #finishCodeDeposit ACCT OUT
         => #popCallStack ~> #if EXECMODE ==K VMTESTS #then #popWorldState #else #dropWorldState #fi ~> #dropSubstate
         ~> #refund GAVAIL ~> ACCT ~> #push
        ...
        </k>
        <mode> EXECMODE </mode>
        <gas> GAVAIL </gas>
        <account>
          <acctID> ACCT </acctID>
          <code> _ => OUT </code>
          ...
        </account>
        <activeAccounts> ... ACCT |-> (EMPTY => #if OUT =/=K .WordStack #then false #else EMPTY #fi) ... </activeAccounts>

   rule <k> #exception ~> #finishCodeDeposit ACCT _
         => #popCallStack ~> #if EXECMODE ==K VMTESTS #then #popWorldState #else #dropWorldState #fi ~> #dropSubstate
         ~> #refund GAVAIL ~> ACCT ~> #push
        ...
        </k>
        <mode> EXECMODE </mode>
        <gas> GAVAIL </gas>
        <schedule> FRONTIER </schedule>

   rule <k> #exception ~> #finishCodeDeposit _ _ => #popCallStack ~> #popWorldState ~> #popSubstate ~> 0 ~> #push ... </k>
        <schedule> SCHED </schedule>
     requires SCHED =/=K FRONTIER

CREATE will attempt to #create the account using the initialization code and cleans up the result with #codeDeposit.

   syntax TernStackOp ::= "CREATE"
// -------------------------------
   rule <k> CREATE VALUE MEMSTART MEMWIDTH
         => #checkCreate ACCT VALUE
         ~> #create ACCT #newAddr(ACCT, NONCE) #ifInt Gstaticcalldepth << SCHED >> #then GAVAIL #else #allBut64th(GAVAIL) #fi VALUE #range(LM, MEMSTART, MEMWIDTH)
         ~> #codeDeposit #newAddr(ACCT, NONCE)
        ...
        </k>
        <schedule> SCHED </schedule>
        <id> ACCT </id>
        <gas> GAVAIL => #ifInt Gstaticcalldepth << SCHED >> #then 0 #else GAVAIL /Int 64 #fi </gas>
        <localMem> LM </localMem>
        <account>
          <acctID> ACCT </acctID>
          <nonce> NONCE </nonce>
          ...
        </account>

SELFDESTRUCT marks the current account for deletion and transfers funds out of the current account. Self destructing to yourself, unlike a regular transfer, destroys the balance in the account, irreparably losing it.

   syntax UnStackOp ::= "SELFDESTRUCT"
// -----------------------------------
   rule <k> SELFDESTRUCT ACCTTO => #transferFunds ACCT ACCTTO BALFROM ~> #end ... </k>
        <schedule> SCHED </schedule>
        <id> ACCT </id>
        <selfDestruct> SDS (.Set => SetItem(ACCT)) </selfDestruct>
        <refund> RF => #ifInt ACCT in SDS #then RF #else RF +Word Rselfdestruct < SCHED > #fi </refund>
        <account>
          <acctID> ACCT </acctID>
          <balance> BALFROM </balance>
          ...
        </account>
     requires ACCT =/=Int ACCTTO

   rule <k> SELFDESTRUCT ACCT => #end ... </k>
        <schedule> SCHED </schedule>
        <id> ACCT </id>
        <selfDestruct> SDS (.Set => SetItem(ACCT)) </selfDestruct>
        <refund> RF => #ifInt ACCT in SDS #then RF #else RF +Word Rselfdestruct < SCHED > #fi </refund>
        <account>
          <acctID> ACCT </acctID>
          <balance> BALFROM => 0 </balance>
          <nonce> NONCE </nonce>
          <code> CODE </code>
          ...
        </account>
        <activeAccounts> ... ACCT |-> (_ => NONCE ==Int 0 andBool CODE ==K .WordStack) ... </activeAccounts>

Precompiled Contracts

  • #precompiled is a placeholder for the 4 pre-compiled contracts at addresses 1 through 4.
   syntax NullStackOp   ::= PrecompiledOp
   syntax PrecompiledOp ::= #precompiled ( Int ) [function]
// --------------------------------------------------------
   rule #precompiled(1) => ECREC
   rule #precompiled(2) => SHA256
   rule #precompiled(3) => RIP160
   rule #precompiled(4) => ID
  • ECREC performs ECDSA public key recovery.
  • SHA256 performs the SHA2-257 hash function.
  • RIP160 performs the RIPEMD-160 hash function.
  • ID is the identity function (copies input to output).
   syntax PrecompiledOp ::= "ECREC"
// --------------------------------
   rule <k> ECREC => #end ... </k>
        <callData> DATA </callData>
        <output> _ => #ecrec(#sender(#unparseByteStack(DATA [ 0 .. 32 ]), #asWord(DATA [ 32 .. 32 ]), #unparseByteStack(DATA [ 64 .. 32 ]), #unparseByteStack(DATA [ 96 .. 32 ]))) </output>

   syntax WordStack ::= #ecrec ( Account ) [function]
// --------------------------------------------------
   rule #ecrec(.Account) => .WordStack
   rule #ecrec(N:Int)    => #padToWidth(32, #asByteStack(N))

   syntax PrecompiledOp ::= "SHA256"
// ---------------------------------
   rule <k> SHA256 => #end ... </k>
        <callData> DATA </callData>
        <output> _ => #parseHexBytes(Sha256(#unparseByteStack(DATA))) </output>

   syntax PrecompiledOp ::= "RIP160"
// ---------------------------------
   rule <k> RIP160 => #end ... </k>
        <callData> DATA </callData>
        <output> _ => #padToWidth(32, #parseHexBytes(RipEmd160(#unparseByteStack(DATA)))) </output>

   syntax PrecompiledOp ::= "ID"
// -----------------------------
   rule <k> ID => #end ... </k>
        <callData> DATA </callData>
        <output> _ => DATA </output>

Ethereum Gas Calculation

The gas calculation is designed to mirror the style of the yellowpaper. Gas is consumed either by increasing the amount of memory being used, or by executing opcodes.

Memory Consumption

Memory consumed is tracked to determine the appropriate amount of gas to charge for each operation. In the yellowpaper, each opcode is defined to consume zero gas unless specified otherwise next to the semantics of the opcode (appendix H).

  • #memory computes the new memory size given the old size and next operator (with its arguments).
  • #memoryUsageUpdate is the function M in appendix H of the yellowpaper which helps track the memory used.
   syntax Int ::= #memory ( OpCode , Int ) [function]
// --------------------------------------------------
   rule #memory ( MLOAD INDEX     , MU ) => #memoryUsageUpdate(MU, INDEX, 32)
   rule #memory ( MSTORE INDEX _  , MU ) => #memoryUsageUpdate(MU, INDEX, 32)
   rule #memory ( MSTORE8 INDEX _ , MU ) => #memoryUsageUpdate(MU, INDEX, 1)

   rule #memory ( SHA3 START WIDTH   , MU ) => #memoryUsageUpdate(MU, START, WIDTH)
   rule #memory ( LOG(_) START WIDTH , MU ) => #memoryUsageUpdate(MU, START, WIDTH)

   rule #memory ( CODECOPY START _ WIDTH      , MU ) => #memoryUsageUpdate(MU, START, WIDTH)
   rule #memory ( EXTCODECOPY _ START _ WIDTH , MU ) => #memoryUsageUpdate(MU, START, WIDTH)
   rule #memory ( CALLDATACOPY START _ WIDTH  , MU ) => #memoryUsageUpdate(MU, START, WIDTH)

   rule #memory ( CREATE _ START WIDTH , MU ) => #memoryUsageUpdate(MU, START, WIDTH)
   rule #memory ( RETURN START WIDTH   , MU ) => #memoryUsageUpdate(MU, START, WIDTH)

   rule #memory ( COP:CallOp     _ _ _ ARGSTART ARGWIDTH RETSTART RETWIDTH , MU ) => #memoryUsageUpdate(#memoryUsageUpdate(MU, ARGSTART, ARGWIDTH), RETSTART, RETWIDTH)
   rule #memory ( CSOP:CallSixOp _ _   ARGSTART ARGWIDTH RETSTART RETWIDTH , MU ) => #memoryUsageUpdate(#memoryUsageUpdate(MU, ARGSTART, ARGWIDTH), RETSTART, RETWIDTH)

Grumble grumble, K sucks at owise.

   rule #memory(JUMP _,    MU) => MU
   rule #memory(JUMPI _ _, MU) => MU
   rule #memory(JUMPDEST,  MU) => MU

   rule #memory(SSTORE _ _,   MU) => MU
   rule #memory(SLOAD _,      MU) => MU

   rule #memory(ADD _ _,        MU) => MU
   rule #memory(SUB _ _,        MU) => MU
   rule #memory(MUL _ _,        MU) => MU
   rule #memory(DIV _ _,        MU) => MU
   rule #memory(EXP _ _,        MU) => MU
   rule #memory(MOD _ _,        MU) => MU
   rule #memory(SDIV _ _,       MU) => MU
   rule #memory(SMOD _ _,       MU) => MU
   rule #memory(SIGNEXTEND _ _, MU) => MU
   rule #memory(ADDMOD _ _ _,   MU) => MU
   rule #memory(MULMOD _ _ _,   MU) => MU

   rule #memory(NOT _,     MU) => MU
   rule #memory(AND _ _,   MU) => MU
   rule #memory(EVMOR _ _, MU) => MU
   rule #memory(XOR _ _,   MU) => MU
   rule #memory(BYTE _ _,  MU) => MU
   rule #memory(ISZERO _,  MU) => MU

   rule #memory(LT _ _,         MU) => MU
   rule #memory(GT _ _,         MU) => MU
   rule #memory(SLT _ _,        MU) => MU
   rule #memory(SGT _ _,        MU) => MU
   rule #memory(EQ _ _,         MU) => MU

   rule #memory(POP _,      MU) => MU
   rule #memory(PUSH(_, _), MU) => MU
   rule #memory(DUP(_) _,   MU) => MU
   rule #memory(SWAP(_) _,  MU) => MU

   rule #memory(STOP,         MU) => MU
   rule #memory(ADDRESS,      MU) => MU
   rule #memory(ORIGIN,       MU) => MU
   rule #memory(CALLER,       MU) => MU
   rule #memory(CALLVALUE,    MU) => MU
   rule #memory(CALLDATASIZE, MU) => MU
   rule #memory(CODESIZE,     MU) => MU
   rule #memory(GASPRICE,     MU) => MU
   rule #memory(COINBASE,     MU) => MU
   rule #memory(TIMESTAMP,    MU) => MU
   rule #memory(NUMBER,       MU) => MU
   rule #memory(DIFFICULTY,   MU) => MU
   rule #memory(GASLIMIT,     MU) => MU
   rule #memory(PC,           MU) => MU
   rule #memory(MSIZE,        MU) => MU
   rule #memory(GAS,          MU) => MU

   rule #memory(SELFDESTRUCT _, MU) => MU
   rule #memory(CALLDATALOAD _, MU) => MU
   rule #memory(EXTCODESIZE _,  MU) => MU
   rule #memory(BALANCE _,      MU) => MU
   rule #memory(BLOCKHASH _,    MU) => MU

   rule #memory(_:PrecompiledOp, MU) => MU

   syntax Int ::= #memoryUsageUpdate ( Int , Int , Int ) [function]
// ----------------------------------------------------------------
   rule #memoryUsageUpdate(MU, START, 0)     => MU
   rule #memoryUsageUpdate(MU, START, WIDTH) => maxInt(MU, (START +Int WIDTH) up/Int 32) requires WIDTH >Int 0

Execution Gas

Each opcode has an intrinsic gas cost of execution as well (appendix H of the yellowpaper).

  • #gasExec loads all the relevant surronding state and uses that to compute the intrinsic execution gas of each opcode.
   syntax InternalOp ::= #gasExec ( Schedule , OpCode )
// ----------------------------------------------------
   rule <k> #gasExec(SCHED, SSTORE INDEX VALUE) => Csstore(SCHED, VALUE, #lookup(STORAGE, INDEX)) ... </k>
        <id> ACCT </id>
        <account>
          <acctID> ACCT </acctID>
          <storage> STORAGE </storage>
          ...
        </account>

   rule <k> #gasExec(SCHED, EXP W0 0)  => Gexp < SCHED > ... </k>
   rule <k> #gasExec(SCHED, EXP W0 W1) => Gexp < SCHED > +Int (Gexpbyte < SCHED > *Int (1 +Int (log256Int(W1)))) ... </k> requires W1 =/=K 0

   rule <k> #gasExec(SCHED, CALLDATACOPY  _ _ WIDTH) => Gverylow     < SCHED > +Int (Gcopy < SCHED > *Int (WIDTH up/Int 32)) ... </k>
   rule <k> #gasExec(SCHED, CODECOPY      _ _ WIDTH) => Gverylow     < SCHED > +Int (Gcopy < SCHED > *Int (WIDTH up/Int 32)) ... </k>
   rule <k> #gasExec(SCHED, EXTCODECOPY _ _ _ WIDTH) => Gextcodecopy < SCHED > +Int (Gcopy < SCHED > *Int (WIDTH up/Int 32)) ... </k>

   rule <k> #gasExec(SCHED, LOG(N) _ WIDTH) => (Glog < SCHED > +Int (Glogdata < SCHED > *Int WIDTH) +Int (N *Int Glogtopic < SCHED >)) ... </k>

   rule <k> #gasExec(SCHED, CALL         GCAP ACCTTO VALUE _ _ _ _) => Ccall(SCHED, ACCTTO,   ACCTS, GCAP, GAVAIL, VALUE) ... </k>
        <activeAccounts> ACCTS </activeAccounts>
        <gas> GAVAIL </gas>

   rule <k> #gasExec(SCHED, CALLCODE     GCAP _      VALUE _ _ _ _) => Ccall(SCHED, ACCTFROM, ACCTS, GCAP, GAVAIL, VALUE) ... </k>
        <id> ACCTFROM </id>
        <activeAccounts> ACCTS </activeAccounts>
        <gas> GAVAIL </gas>

   rule <k> #gasExec(SCHED, DELEGATECALL GCAP _ _ _ _ _) => Ccall(SCHED, ACCTFROM, ACCTS, GCAP, GAVAIL, 0) ... </k>
        <id> ACCTFROM </id>
        <activeAccounts> ACCTS </activeAccounts>
        <gas> GAVAIL </gas>

   rule <k> #gasExec(SCHED, SELFDESTRUCT ACCTTO) => Cselfdestruct(SCHED, ACCTTO, ACCTS, BAL) ... </k>
        <activeAccounts> ACCTS </activeAccounts>
        <id> ACCTFROM </id>
        <account>
          <acctID> ACCTFROM </acctID>
          <balance> BAL </balance>
          ...
        </account>

   rule <k> #gasExec(SCHED, CREATE _ _ _) => Gcreate < SCHED > ... </k>

   rule <k> #gasExec(SCHED, SHA3 _ WIDTH) => Gsha3 < SCHED > +Int (Gsha3word < SCHED > *Int (WIDTH up/Int 32)) ... </k>

   rule <k> #gasExec(SCHED, JUMPDEST) => Gjumpdest < SCHED > ... </k>
   rule <k> #gasExec(SCHED, SLOAD _)  => Gsload    < SCHED > ... </k>

   // Wzero
   rule <k> #gasExec(SCHED, STOP)       => Gzero < SCHED > ... </k>
   rule <k> #gasExec(SCHED, RETURN _ _) => Gzero < SCHED > ... </k>

   // Wbase
   rule <k> #gasExec(SCHED, ADDRESS)      => Gbase < SCHED > ... </k>
   rule <k> #gasExec(SCHED, ORIGIN)       => Gbase < SCHED > ... </k>
   rule <k> #gasExec(SCHED, CALLER)       => Gbase < SCHED > ... </k>
   rule <k> #gasExec(SCHED, CALLVALUE)    => Gbase < SCHED > ... </k>
   rule <k> #gasExec(SCHED, CALLDATASIZE) => Gbase < SCHED > ... </k>
   rule <k> #gasExec(SCHED, CODESIZE)     => Gbase < SCHED > ... </k>
   rule <k> #gasExec(SCHED, GASPRICE)     => Gbase < SCHED > ... </k>
   rule <k> #gasExec(SCHED, COINBASE)     => Gbase < SCHED > ... </k>
   rule <k> #gasExec(SCHED, TIMESTAMP)    => Gbase < SCHED > ... </k>
   rule <k> #gasExec(SCHED, NUMBER)       => Gbase < SCHED > ... </k>
   rule <k> #gasExec(SCHED, DIFFICULTY)   => Gbase < SCHED > ... </k>
   rule <k> #gasExec(SCHED, GASLIMIT)     => Gbase < SCHED > ... </k>
   rule <k> #gasExec(SCHED, POP _)        => Gbase < SCHED > ... </k>
   rule <k> #gasExec(SCHED, PC)           => Gbase < SCHED > ... </k>
   rule <k> #gasExec(SCHED, MSIZE)        => Gbase < SCHED > ... </k>
   rule <k> #gasExec(SCHED, GAS)          => Gbase < SCHED > ... </k>

   // Wverylow
   rule <k> #gasExec(SCHED, ADD _ _)        => Gverylow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, SUB _ _)        => Gverylow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, NOT _)          => Gverylow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, LT _ _)         => Gverylow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, GT _ _)         => Gverylow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, SLT _ _)        => Gverylow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, SGT _ _)        => Gverylow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, EQ _ _)         => Gverylow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, ISZERO _)       => Gverylow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, AND _ _)        => Gverylow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, EVMOR _ _)      => Gverylow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, XOR _ _)        => Gverylow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, BYTE _ _)       => Gverylow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, CALLDATALOAD _) => Gverylow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, MLOAD _)        => Gverylow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, MSTORE _ _)     => Gverylow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, MSTORE8 _ _)    => Gverylow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, PUSH(_, _))     => Gverylow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, DUP(_) _)       => Gverylow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, SWAP(_) _)      => Gverylow < SCHED > ... </k>

   // Wlow
   rule <k> #gasExec(SCHED, MUL _ _)        => Glow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, DIV _ _)        => Glow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, SDIV _ _)       => Glow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, MOD _ _)        => Glow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, SMOD _ _)       => Glow < SCHED > ... </k>
   rule <k> #gasExec(SCHED, SIGNEXTEND _ _) => Glow < SCHED > ... </k>

   // Wmid
   rule <k> #gasExec(SCHED, ADDMOD _ _ _) => Gmid < SCHED > ... </k>
   rule <k> #gasExec(SCHED, MULMOD _ _ _) => Gmid < SCHED > ... </k>
   rule <k> #gasExec(SCHED, JUMP _) => Gmid < SCHED > ... </k>

   // Whigh
   rule <k> #gasExec(SCHED, JUMPI _ _) => Ghigh < SCHED > ... </k>

   rule <k> #gasExec(SCHED, EXTCODESIZE _) => Gextcodesize < SCHED > ... </k>
   rule <k> #gasExec(SCHED, BALANCE _)     => Gbalance     < SCHED > ... </k>
   rule <k> #gasExec(SCHED, BLOCKHASH _)   => Gblockhash   < SCHED > ... </k>

   // Precompiled
   rule <k> #gasExec(_, ECREC)  => 3000 ... </k>
   rule <k> #gasExec(_, SHA256) =>  60 +Int  12 *Int (#sizeWordStack(DATA) up/Int 32) ... </k> <callData> DATA </callData>
   rule <k> #gasExec(_, RIP160) => 600 +Int 120 *Int (#sizeWordStack(DATA) up/Int 32) ... </k> <callData> DATA </callData>
   rule <k> #gasExec(_, ID)     =>  15 +Int   3 *Int (#sizeWordStack(DATA) up/Int 32) ... </k> <callData> DATA </callData>

There are several helpers for calculating gas (most of them also specified in the yellowpaper).

Note: These are all functions as the operator #gasExec has already loaded all the relevant state.

   syntax Int ::= Csstore ( Schedule , Int , Int ) [function]
// ----------------------------------------------------------
   rule Csstore(SCHED, VALUE, OLD) => #ifInt VALUE =/=Int 0 andBool OLD ==Int 0 #then Gsstoreset < SCHED > #else Gsstorereset < SCHED > #fi

   syntax Int ::= Ccall    ( Schedule , Int , Map , Int , Int , Int ) [function]
                | Ccallgas ( Schedule , Int , Map , Int , Int , Int ) [function]
                | Cgascap  ( Schedule , Int , Int , Int )             [function]
                | Cextra   ( Schedule , Int , Map , Int )             [function]
                | Cxfer    ( Schedule , Int )                         [function]
                | Cnew     ( Schedule , Int , Map , Int )             [function]
// -----------------------------------------------------------------------------
   rule Ccall(SCHED, ACCT, ACCTS, GCAP, GAVAIL, VALUE) => Cextra(SCHED, ACCT, ACCTS, VALUE) +Int Cgascap(SCHED, GCAP, GAVAIL, Cextra(SCHED, ACCT, ACCTS, VALUE))

   rule Ccallgas(SCHED, ACCT, ACCTS, GCAP, GAVAIL, 0)     => Cgascap(SCHED, GCAP, GAVAIL, Cextra(SCHED, ACCT, ACCTS,     0))
   rule Ccallgas(SCHED, ACCT, ACCTS, GCAP, GAVAIL, VALUE) => Cgascap(SCHED, GCAP, GAVAIL, Cextra(SCHED, ACCT, ACCTS, VALUE)) +Int Gcallstipend < SCHED > requires VALUE =/=K 0

   rule Cgascap(SCHED, GCAP, GAVAIL, GEXTRA) => minInt(#allBut64th(GAVAIL -Int GEXTRA), GCAP) requires GAVAIL >=Int GEXTRA andBool notBool Gstaticcalldepth << SCHED >>
   rule Cgascap(SCHED, GCAP, GAVAIL, GEXTRA) => GCAP                                          requires GAVAIL <Int  GEXTRA orBool Gstaticcalldepth << SCHED >>

   rule Cextra(SCHED, ACCT, ACCTS, VALUE) => Gcall < SCHED > +Int Cnew(SCHED, ACCT, ACCTS, VALUE) +Int Cxfer(SCHED, VALUE)

   rule Cxfer(SCHED, 0) => 0
   rule Cxfer(SCHED, N) => Gcallvalue < SCHED > requires N =/=K 0

   rule Cnew(SCHED, ACCT, ACCTS, VALUE) => Gnewaccount < SCHED >
     requires         #accountNonexistent(SCHED, ACCT, ACCTS) andBool (VALUE =/=Int 0 orBool          Gzerovaluenewaccountgas << SCHED >>)
   rule Cnew(SCHED, ACCT, ACCTS, VALUE) => 0
     requires notBool #accountNonexistent(SCHED, ACCT, ACCTS) orBool  (VALUE  ==Int 0 andBool notBool Gzerovaluenewaccountgas << SCHED >>)

   syntax Int ::= Cselfdestruct ( Schedule , Int , Map , Int ) [function]
// ----------------------------------------------------------------------
   rule Cselfdestruct(SCHED, ACCT, ACCTS, BAL) => Gselfdestruct < SCHED > +Int Gnewaccount < SCHED >
     requires (#accountNonexistent(SCHED, ACCT, ACCTS)) andBool (        Gselfdestructnewaccount << SCHED >>) andBool (BAL =/=Int 0 orBool          Gzerovaluenewaccountgas << SCHED >>)
   rule Cselfdestruct(SCHED, ACCT, ACCTS, BAL) => Gselfdestruct < SCHED >
     requires (#accountNonexistent(SCHED, ACCT, ACCTS)) andBool (notBool Gselfdestructnewaccount << SCHED >>  orBool  (BAL  ==Int 0 andBool notBool Gzerovaluenewaccountgas << SCHED >>))
   rule Cselfdestruct(SCHED, ACCT, ACCTS, BAL) => Gselfdestruct < SCHED >
     requires notBool #accountNonexistent(SCHED, ACCT, ACCTS)

   syntax Bool ::= #accountNonexistent ( Schedule , Int , Map ) [function]
                 | #accountEmpty ( Int , Map )                  [function]
// -----------------------------------------------------------------------
   rule #accountNonexistent(SCHED, ACCT, ACCTS) => notBool ACCT in_keys(ACCTS) orBool (#accountEmpty(ACCT, ACCTS) andBool Gemptyisnonexistent << SCHED >>)
   rule #accountEmpty(ACCT, (ACCT |-> EMPTY) _) => EMPTY

   syntax Int ::= #allBut64th ( Int ) [function]
// ---------------------------------------------
   rule #allBut64th(N) => N -Int (N /Int 64)

   syntax Int ::= G0 ( Schedule , WordStack , Bool ) [function]
// ------------------------------------------------------------
   rule G0(SCHED, .WordStack, true)  => Gtxcreate    < SCHED >
   rule G0(SCHED, .WordStack, false) => Gtransaction < SCHED >

   rule G0(SCHED, 0 : REST, ISCREATE) => Gtxdatazero    < SCHED > +Int G0(SCHED, REST, ISCREATE)
   rule G0(SCHED, N : REST, ISCREATE) => Gtxdatanonzero < SCHED > +Int G0(SCHED, REST, ISCREATE) requires N =/=Int 0

   syntax Int ::= "G*" "(" Int "," Int "," Int ")" [function]
// ----------------------------------------------------------
   rule G*(GAVAIL, GLIMIT, REFUND) => GAVAIL +Int minInt((GLIMIT -Int GAVAIL)/Int 2, REFUND)

Fee Schedule from C++ Implementation

The C++ Implementation of EVM specifies several different “profiles” for how the VM works. Here we provide each protocol from the C++ implementation, as the yellowpaper does not contain all the different profiles. Specify which profile by passing in the argument -cSCHEDULE=<FEE_SCHEDULE> when calling krun (the available <FEE_SCHEDULE> are supplied here).

A ScheduleFlag is a boolean determined by the fee schedule; applying a ScheduleFlag to a Schedule yields whether the flag is set or not.

   syntax Bool ::= ScheduleFlag "<<" Schedule ">>" [function]
// ----------------------------------------------------------

   syntax ScheduleFlag ::= "Gselfdestructnewaccount" | "Gstaticcalldepth" | "Gemptyisnonexistent" | "Gzerovaluenewaccountgas"
// --------------------------------------------------------------------------------------------------------------------------

A ScheduleConst is a constant determined by the fee schedule; applying a ScheduleConst to a Schedule yields the correct constant for that schedule.

   syntax Int ::= ScheduleConst "<" Schedule ">" [function]
// --------------------------------------------------------

   syntax ScheduleConst ::= "Gzero"        | "Gbase"        | "Gverylow"      | "Glow"           | "Gmid"         | "Ghigh"
                          | "Gextcodesize" | "Gextcodecopy" | "Gbalance"      | "Gsload"         | "Gjumpdest"    | "Gsstoreset"
                          | "Gsstorereset" | "Rsstoreclear" | "Rselfdestruct" | "Gselfdestruct"  | "Gcreate"      | "Gcodedeposit"
                          | "Gcall"        | "Gcallvalue"   | "Gcallstipend"  | "Gnewaccount"    | "Gexp"         | "Gexpbyte"
                          | "Gmemory"      | "Gtxcreate"    | "Gtxdatazero"   | "Gtxdatanonzero" | "Gtransaction" | "Glog"          | "Glogdata"
                          | "Glogtopic"    | "Gsha3"        | "Gsha3word"     | "Gcopy"          | "Gblockhash"   | "Gquadcoeff"    | "maxCodeSize"
// ------------------------------------------------------------------------------------------------------------------------------------------------

Defualt Schedule

   syntax Schedule ::= "DEFAULT"
// -----------------------------
   rule Gzero    < DEFAULT > => 0
   rule Gbase    < DEFAULT > => 2
   rule Gverylow < DEFAULT > => 3
   rule Glow     < DEFAULT > => 5
   rule Gmid     < DEFAULT > => 8
   rule Ghigh    < DEFAULT > => 10

   rule Gexp      < DEFAULT > => 10
   rule Gexpbyte  < DEFAULT > => 10
   rule Gsha3     < DEFAULT > => 30
   rule Gsha3word < DEFAULT > => 6

   rule Gsload       < DEFAULT > => 50
   rule Gsstoreset   < DEFAULT > => 20000
   rule Gsstorereset < DEFAULT > => 5000
   rule Rsstoreclear < DEFAULT > => 15000

   rule Glog      < DEFAULT > => 375
   rule Glogdata  < DEFAULT > => 8
   rule Glogtopic < DEFAULT > => 375

   rule Gcall        < DEFAULT > => 40
   rule Gcallstipend < DEFAULT > => 2300
   rule Gcallvalue   < DEFAULT > => 9000
   rule Gnewaccount  < DEFAULT > => 25000

   rule Gcreate       < DEFAULT > => 32000
   rule Gcodedeposit  < DEFAULT > => 200
   rule Gselfdestruct < DEFAULT > => 0
   rule Rselfdestruct < DEFAULT > => 24000

   rule Gmemory    < DEFAULT > => 3
   rule Gquadcoeff < DEFAULT > => 512
   rule Gcopy      < DEFAULT > => 3

   rule Gtransaction   < DEFAULT > => 21000
   rule Gtxcreate      < DEFAULT > => 53000
   rule Gtxdatazero    < DEFAULT > => 4
   rule Gtxdatanonzero < DEFAULT > => 68

   rule Gjumpdest    < DEFAULT > => 1
   rule Gbalance     < DEFAULT > => 20
   rule Gblockhash   < DEFAULT > => 20
   rule Gextcodesize < DEFAULT > => 20
   rule Gextcodecopy < DEFAULT > => 20

   rule maxCodeSize < DEFAULT > => 2 ^Int 32 -Int 1

   rule Gselfdestructnewaccount << DEFAULT >> => false
   rule Gstaticcalldepth        << DEFAULT >> => true
   rule Gemptyisnonexistent     << DEFAULT >> => false
   rule Gzerovaluenewaccountgas << DEFAULT >> => true
struct EVMSchedule
{
    EVMSchedule(): tierStepGas(std::array<unsigned, 8>{{0, 2, 3, 5, 8, 10, 20, 0}}) {}
    EVMSchedule(bool _efcd, bool _hdc, unsigned const& _txCreateGas): exceptionalFailedCodeDeposit(_efcd), haveDelegateCall(_hdc), tierStepGas(std::array<unsigned, 8>{{0, 2, 3, 5, 8, 10, 20, 0}}), txCreateGas(_txCreateGas) {}
    bool exceptionalFailedCodeDeposit = true;
    bool haveDelegateCall = true;
    bool eip150Mode = false;
    bool eip158Mode = false;
    bool haveRevert = false;
    bool haveReturnData = false;
    bool haveStaticCall = false;
    bool haveCreate2 = false;
    std::array<unsigned, 8> tierStepGas;

    unsigned expGas = 10;
    unsigned expByteGas = 10;
    unsigned sha3Gas = 30;
    unsigned sha3WordGas = 6;

    unsigned sloadGas = 50;
    unsigned sstoreSetGas = 20000;
    unsigned sstoreResetGas = 5000;
    unsigned sstoreRefundGas = 15000;

    unsigned logGas = 375;
    unsigned logDataGas = 8;
    unsigned logTopicGas = 375;

    unsigned callGas = 40;
    unsigned callStipend = 2300;
    unsigned callValueTransferGas = 9000;
    unsigned callNewAccountGas = 25000;

    unsigned createGas = 32000;
    unsigned createDataGas = 200;
    unsigned suicideGas = 0;
    unsigned suicideRefundGas = 24000;

    unsigned memoryGas = 3;
    unsigned quadCoeffDiv = 512;
    unsigned copyGas = 3;

    unsigned txGas = 21000;
    unsigned txCreateGas = 53000;
    unsigned txDataZeroGas = 4;
    unsigned txDataNonZeroGas = 68;

    unsigned jumpdestGas = 1;
    unsigned balanceGas = 20;
    unsigned blockhashGas = 20;
    unsigned extcodesizeGas = 20;
    unsigned extcodecopyGas = 20;

    unsigned maxCodeSize = unsigned(-1);

    bool staticCallDepthLimit() const { return !eip150Mode; }
    bool suicideNewAccountGas() const { return !eip150Mode; }
    bool suicideChargesNewAccountGas() const { return eip150Mode; }
    bool emptinessIsNonexistence() const { return eip158Mode; }
    bool zeroValueTransferChargesNewAccountGas() const { return !eip158Mode; }
};

Frontier Schedule

   syntax Schedule ::= "FRONTIER"
// ------------------------------
   rule Gtxcreate  < FRONTIER > => 21000
   rule SCHEDCONST < FRONTIER > => SCHEDCONST < DEFAULT > requires SCHEDCONST =/=K Gtxcreate

   rule SCHEDFLAG << FRONTIER >> => SCHEDFLAG << DEFAULT >>
static const EVMSchedule FrontierSchedule = EVMSchedule(false, false, 21000);

Homestead Schedule

   syntax Schedule ::= "HOMESTEAD"
// -------------------------------
   rule SCHEDCONST < HOMESTEAD > => SCHEDCONST < DEFAULT >

   rule SCHEDFLAG << HOMESTEAD >> => SCHEDFLAG << DEFAULT >>
static const EVMSchedule HomesteadSchedule = EVMSchedule(true, true, 53000);

EIP150 Schedule

   syntax Schedule ::= "EIP150"
// ----------------------------
   rule Gbalance      < EIP150 > => 400
   rule Gsload        < EIP150 > => 200
   rule Gcall         < EIP150 > => 700
   rule Gselfdestruct < EIP150 > => 5000
   rule Gextcodesize  < EIP150 > => 700
   rule Gextcodecopy  < EIP150 > => 700

   rule SCHEDCONST    < EIP150 > => SCHEDCONST < HOMESTEAD >
     requires notBool      ( SCHEDCONST ==K Gbalance      orBool SCHEDCONST ==K Gsload       orBool SCHEDCONST ==K Gcall
                      orBool SCHEDCONST ==K Gselfdestruct orBool SCHEDCONST ==K Gextcodesize orBool SCHEDCONST ==K Gextcodecopy
                           )

   rule Gselfdestructnewaccount << EIP150 >> => true
   rule Gstaticcalldepth        << EIP150 >> => false
   rule SCHEDCONST              << EIP150 >> => SCHEDCONST << HOMESTEAD >>
     requires notBool      ( SCHEDCONST ==K Gselfdestructnewaccount orBool SCHEDCONST ==K Gstaticcalldepth )
static const EVMSchedule EIP150Schedule = []
{
    EVMSchedule schedule = HomesteadSchedule;
    schedule.eip150Mode = true;
    schedule.extcodesizeGas = 700;
    schedule.extcodecopyGas = 700;
    schedule.balanceGas = 400;
    schedule.sloadGas = 200;
    schedule.callGas = 700;
    schedule.suicideGas = 5000;
    return schedule;
}();

EIP158 Schedule

   syntax Schedule ::= "EIP158"
// ----------------------------
   rule Gexpbyte    < EIP158 > => 50
   rule maxCodeSize < EIP158 > => 24576

   rule SCHEDCONST  < EIP158 > => SCHEDCONST < EIP150 > requires SCHEDCONST =/=K Gexpbyte andBool SCHEDCONST =/=K maxCodeSize

   rule Gemptyisnonexistent     << EIP158 >> => true
   rule Gzerovaluenewaccountgas << EIP158 >> => false
   rule SCHEDCONST              << EIP158 >> => SCHEDCONST << EIP150 >>
     requires notBool      ( SCHEDCONST ==K Gemptyisnonexistent orBool SCHEDCONST ==K Gzerovaluenewaccountgas )
static const EVMSchedule EIP158Schedule = []
{
    EVMSchedule schedule = EIP150Schedule;
    schedule.expByteGas = 50;
    schedule.eip158Mode = true;
    schedule.maxCodeSize = 0x6000;
    return schedule;
}();

Byzantium Schedule

   syntax Schedule ::= "BYZANTIUM"
// -------------------------------
   rule SCHEDCONST < BYZANTIUM > => SCHEDCONST < EIP158 >

   rule SCHEDFLAG << BYZANTIUM >> => SCHEDFLAG << EIP158 >>
static const EVMSchedule ByzantiumSchedule = []
{
    EVMSchedule schedule = EIP158Schedule;
    schedule.haveRevert = true;
    schedule.haveReturnData = true;
    schedule.haveStaticCall = true;
    schedule.blockRewardOverwrite = {3 * ether};
    return schedule;
}();

Constantinople Schedule

   syntax Schedule ::= "CONSTANTINOPLE"
// ------------------------------------
   rule Gblockhash < CONSTANTINOPLE > => 800
   rule SCHEDCONST < CONSTANTINOPLE > => SCHEDCONST < BYZANTIUM >
     requires SCHEDCONST =/=K Gblockhash

   rule SCHEDFLAG << CONSTANTINOPLE >> => SCHEDFLAG << BYZANTIUM >>
static const EVMSchedule ConstantinopleSchedule = []
{
    EVMSchedule schedule = ByzantiumSchedule;
    schedule.blockhashGas = 800;
    schedule.haveCreate2 = true;
    return schedule;
}();

EVM Program Representations

EVM programs are represented algebraically in K, but programs can load and manipulate program data directly. The opcodes CODECOPY and EXTCODECOPY rely on the assembled form of the programs being present. The opcode CREATE relies on being able to interperet EVM data as a program.

This is a program representation dependence, which we might want to avoid. Perhaps the only program representation dependence we should have is the hash of the program; doing so achieves:

  • Program representation independence (different analysis tools on the language don’t have to ensure they have a common representation of programs, just a common interperetation of the data-files holding programs).
  • Programming language independence (we wouldn’t even have to commit to a particular language or interperetation of the data-file).
  • Only depending on the hash allows us to know that we have exactly the correct data-file (program), and nothing more.

Disassembler

After interpreting the strings representing programs as a WordStack, it should be changed into an OpCodes for use by the EVM semantics.

  • #dasmOpCodes interperets WordStack as an OpCodes.
  • #dasmPUSH handles the case of a PushOp.
  • #dasmOpCode interperets a Int as an OpCode.
    syntax OpCodes ::= #dasmOpCodes ( WordStack , Schedule )           [function]
                     | #dasmOpCodes ( OpCodes , WordStack , Schedule ) [function, klabel(#dasmOpCodesAux)]
                     | #revOpCodes  ( OpCodes , OpCodes )              [function]
 // -----------------------------------------------------------------------------
    rule #dasmOpCodes( WS, SCHED ) => #revOpCodes(#dasmOpCodes(.OpCodes, WS, SCHED), .OpCodes)

    rule #dasmOpCodes( OPS, .WordStack, _ ) => OPS
    rule #dasmOpCodes( OPS, W : WS, SCHED ) => #dasmOpCodes(#dasmOpCode(W, SCHED) ; OPS, WS, SCHED) requires W >=Int 0   andBool W <=Int 95
    rule #dasmOpCodes( OPS, W : WS, SCHED ) => #dasmOpCodes(#dasmOpCode(W, SCHED) ; OPS, WS, SCHED) requires W >=Int 165 andBool W <=Int 255
    rule #dasmOpCodes( OPS, W : WS, SCHED ) => #dasmOpCodes(DUP(W -Int 127)       ; OPS, WS, SCHED) requires W >=Int 128 andBool W <=Int 143
    rule #dasmOpCodes( OPS, W : WS, SCHED ) => #dasmOpCodes(SWAP(W -Int 143)      ; OPS, WS, SCHED) requires W >=Int 144 andBool W <=Int 159
    rule #dasmOpCodes( OPS, W : WS, SCHED ) => #dasmOpCodes(LOG(W -Int 160)       ; OPS, WS, SCHED) requires W >=Int 160 andBool W <=Int 164

    rule #dasmOpCodes( OPS, W : WS, SCHED ) => #dasmOpCodes(PUSH(W -Int 95, #asWord(#take(W -Int 95, WS))) ; OPS, #drop(W -Int 95, WS), SCHED) requires W >=Int 96  andBool W <=Int 127

    rule #revOpCodes ( OP ; OPS , OPS' ) => #revOpCodes(OPS, OP ; OPS')
    rule #revOpCodes ( .OpCodes , OPS  ) => OPS

    syntax OpCode ::= #dasmOpCode ( Int , Schedule ) [function]
 // -----------------------------------------------------------
    rule #dasmOpCode(   0,     _ ) => STOP
    rule #dasmOpCode(   1,     _ ) => ADD
    rule #dasmOpCode(   2,     _ ) => MUL
    rule #dasmOpCode(   3,     _ ) => SUB
    rule #dasmOpCode(   4,     _ ) => DIV
    rule #dasmOpCode(   5,     _ ) => SDIV
    rule #dasmOpCode(   6,     _ ) => MOD
    rule #dasmOpCode(   7,     _ ) => SMOD
    rule #dasmOpCode(   8,     _ ) => ADDMOD
    rule #dasmOpCode(   9,     _ ) => MULMOD
    rule #dasmOpCode(  10,     _ ) => EXP
    rule #dasmOpCode(  11,     _ ) => SIGNEXTEND
    rule #dasmOpCode(  16,     _ ) => LT
    rule #dasmOpCode(  17,     _ ) => GT
    rule #dasmOpCode(  18,     _ ) => SLT
    rule #dasmOpCode(  19,     _ ) => SGT
    rule #dasmOpCode(  20,     _ ) => EQ
    rule #dasmOpCode(  21,     _ ) => ISZERO
    rule #dasmOpCode(  22,     _ ) => AND
    rule #dasmOpCode(  23,     _ ) => EVMOR
    rule #dasmOpCode(  24,     _ ) => XOR
    rule #dasmOpCode(  25,     _ ) => NOT
    rule #dasmOpCode(  26,     _ ) => BYTE
    rule #dasmOpCode(  32,     _ ) => SHA3
    rule #dasmOpCode(  48,     _ ) => ADDRESS
    rule #dasmOpCode(  49,     _ ) => BALANCE
    rule #dasmOpCode(  50,     _ ) => ORIGIN
    rule #dasmOpCode(  51,     _ ) => CALLER
    rule #dasmOpCode(  52,     _ ) => CALLVALUE
    rule #dasmOpCode(  53,     _ ) => CALLDATALOAD
    rule #dasmOpCode(  54,     _ ) => CALLDATASIZE
    rule #dasmOpCode(  55,     _ ) => CALLDATACOPY
    rule #dasmOpCode(  56,     _ ) => CODESIZE
    rule #dasmOpCode(  57,     _ ) => CODECOPY
    rule #dasmOpCode(  58,     _ ) => GASPRICE
    rule #dasmOpCode(  59,     _ ) => EXTCODESIZE
    rule #dasmOpCode(  60,     _ ) => EXTCODECOPY
    rule #dasmOpCode(  64,     _ ) => BLOCKHASH
    rule #dasmOpCode(  65,     _ ) => COINBASE
    rule #dasmOpCode(  66,     _ ) => TIMESTAMP
    rule #dasmOpCode(  67,     _ ) => NUMBER
    rule #dasmOpCode(  68,     _ ) => DIFFICULTY
    rule #dasmOpCode(  69,     _ ) => GASLIMIT
    rule #dasmOpCode(  80,     _ ) => POP
    rule #dasmOpCode(  81,     _ ) => MLOAD
    rule #dasmOpCode(  82,     _ ) => MSTORE
    rule #dasmOpCode(  83,     _ ) => MSTORE8
    rule #dasmOpCode(  84,     _ ) => SLOAD
    rule #dasmOpCode(  85,     _ ) => SSTORE
    rule #dasmOpCode(  86,     _ ) => JUMP
    rule #dasmOpCode(  87,     _ ) => JUMPI
    rule #dasmOpCode(  88,     _ ) => PC
    rule #dasmOpCode(  89,     _ ) => MSIZE
    rule #dasmOpCode(  90,     _ ) => GAS
    rule #dasmOpCode(  91,     _ ) => JUMPDEST
    rule #dasmOpCode( 240,     _ ) => CREATE
    rule #dasmOpCode( 241,     _ ) => CALL
    rule #dasmOpCode( 242,     _ ) => CALLCODE
    rule #dasmOpCode( 243,     _ ) => RETURN
    rule #dasmOpCode( 244, SCHED ) => DELEGATECALL requires SCHED =/=K FRONTIER
    rule #dasmOpCode( 255,     _ ) => SELFDESTRUCT
    rule #dasmOpCode(   W,     _ ) => INVALID [owise]
endmodule