Web3 RPC JSON Handler
=====================
```k
requires "evm.k"
requires "state-loader.k"
requires "json.k"
```
```k
module WEB3
imports STATE-LOADER
imports JSON-RPC
configuration
.Phase
.Map
.Map
0
0
.List
.Map
0
"":String
0
.List
.ByteArray
0
0
.Account
0
0
0
0
0
.List
.List
$SHUTDOWNABLE:Bool
```
The Blockchain State
--------------------
A `BlockchainItem` contains the information of a block and its network state.
The `blockList` cell stores a list of previous blocks and network states.
- `#pushBlockchainState` saves a copy of the block state and network state as a `BlockchainItem` in the `blockList` cell.
- `#getBlockByNumber(BlockIdentifier, List, Block)` retrieves a specific `BlockchainItem` from the `blockList` cell.
```k
syntax BlockchainItem ::= ".BlockchainItem"
| "{" NetworkCell "|" BlockCell "}"
// -----------------------------------------------------------
syntax KItem ::= "#pushBlockchainState"
// ---------------------------------------
rule #pushBlockchainState => . ...
(.List => ListItem({ NETWORK | BLOCK })) ...
NETWORK
BLOCK
syntax BlockchainItem ::= #getBlockByNumber ( BlockIdentifier , List , BlockchainItem ) [function]
// --------------------------------------------------------------------------------------------------
rule #getBlockByNumber( BLOCKID , .List , BLOCK ) => .BlockchainItem requires BLOCKID =/=K LATEST andBool BLOCKID =/=K PENDING
rule #getBlockByNumber( LATEST , .List , BLOCK ) => BLOCK
rule #getBlockByNumber( LATEST , ListItem( BLOCK ) _ , _ ) => BLOCK
rule #getBlockByNumber( PENDING , _ , BLOCK ) => BLOCK
rule #getBlockByNumber( EARLIEST , _ ListItem( BLOCK ) , _ ) => BLOCK
rule #getBlockByNumber(BLOCKNUM:Int , ListItem({ _ | BLOCKNUM ... } #as BLOCK) REST, _ ) => BLOCK
rule #getBlockByNumber(BLOCKNUM':Int, (ListItem({ _ | BLOCKNUM ... } ) => .List) _, _ )
requires BLOCKNUM =/=Int BLOCKNUM'
syntax AccountItem ::= AccountCell | ".AccountItem"
// ---------------------------------------------------
syntax AccountItem ::= #getAccountFromBlockchainItem( BlockchainItem , Int ) [function]
// ---------------------------------------------------------------------------------------
rule #getAccountFromBlockchainItem ( { ( ACCT ACCOUNTDATA ) ... ... | _ } , ACCT ) => ACCT ACCOUNTDATA
rule #getAccountFromBlockchainItem(_, _) => .AccountItem [owise]
syntax KItem ::= #getAccountAtBlock ( BlockIdentifier , Int )
// -------------------------------------------------------------
rule #getAccountAtBlock(BLOCKNUM , ACCTID)
=> #getAccountFromBlockchainItem(#getBlockByNumber(BLOCKNUM, BLOCKLIST, { NETWORK | BLOCK }), ACCTID) ...
BLOCKLIST
NETWORK
BLOCK
```
WEB3 JSON RPC
-------------
```k
syntax JSON ::= #getJSON ( JSONKey , JSON ) [function]
// ------------------------------------------------------
rule #getJSON( KEY, { KEY : J, _ } ) => J
rule #getJSON( _, { .JSONs } ) => undef
rule #getJSON( KEY, { KEY2 : _, REST } ) => #getJSON( KEY, { REST } )
requires KEY =/=K KEY2
syntax Int ::= #getInt ( JSONKey , JSON ) [function]
// ----------------------------------------------------
rule #getInt( KEY, J ) => {#getJSON( KEY, J )}:>Int
syntax String ::= #getString ( JSONKey , JSON ) [function]
// ----------------------------------------------------------
rule #getString( KEY, J ) => {#getJSON( KEY, J )}:>String
syntax Bool ::= isJSONUndef ( JSON ) [function]
// -----------------------------------------------
rule isJSONUndef(J) => J ==K undef
syntax IOJSON ::= JSON | IOError
// --------------------------------
syntax EthereumSimulation ::= accept() [symbol]
// -----------------------------------------------
rule accept() => getRequest() ...
SOCK
_ => #accept(SOCK)
syntax KItem ::= getRequest()
// -----------------------------
rule getRequest() => #loadRPCCall(#getRequest(SOCK)) ...
SOCK
_ => undef
syntax IOJSON ::= #getRequest(Int) [function, hook(JSON.read)]
// --------------------------------------------------------------
syntax K ::= #putResponse(JSON, Int) [function, hook(JSON.write)]
// -----------------------------------------------------------------
syntax IOJSON ::= #putResponseError ( JSON ) [klabel(JSON-RPC_putResponseError), symbol]
// ----------------------------------------------------------------------------------------
syntax KItem ::= #loadRPCCall(IOJSON)
// -------------------------------------
rule #loadRPCCall({ _ } #as J) => #checkRPCCall ~> #runRPCCall ...
_ => #getJSON("jsonrpc", J)
_ => #getJSON("id" , J)
_ => #getJSON("method" , J)
_ => #getJSON("params" , J)
rule #loadRPCCall(#EOF) => #shutdownWrite(SOCK) ~> #close(SOCK) ~> accept() ...
SOCK
rule #loadRPCCall([ _, _ ] #as J) => #loadFromBatch ...
_ => J
_ => .List
rule #loadRPCCall(_:String #Or null #Or _:Int #Or [ .JSONs ]) => #rpcResponseError(-32600, "Invalid Request") ...
_ => null
rule #loadRPCCall(undef) => #rpcResponseError(-32700, "Parse error") ...
_ => null
syntax KItem ::= "#loadFromBatch"
// ---------------------------------
rule #loadFromBatch ~> _ => #loadRPCCall(J)
[ J , JS => JS ]
rule #loadFromBatch ~> _ => #putResponse(List2JSON(RESPONSE), SOCK) ~> getRequest()
[ .JSONs ]
SOCK
RESPONSE
requires size(RESPONSE) >Int 0
rule #loadFromBatch ~> _ => getRequest()
[ .JSONs ]
.List
syntax JSON ::= List2JSON(List) [function]
| List2JSON(List, JSONs) [function, klabel(List2JSONAux)]
// -----------------------------------------------------------------------
rule List2JSON(L) => List2JSON(L, .JSONs)
rule List2JSON(L ListItem(J), JS) => List2JSON(L, (J, JS))
rule List2JSON(.List , JS) => [ JS ]
syntax KItem ::= #sendResponse ( JSONs )
// ----------------------------------------
rule #sendResponse(J) ~> _ => #putResponse({ "jsonrpc": "2.0", "id": CALLID, J }, SOCK) ~> getRequest()
CALLID
SOCK
undef
requires CALLID =/=K undef
rule #sendResponse(_) ~> _ => getRequest()
undef
undef
rule #sendResponse(J) ~> _ => #loadFromBatch
CALLID
[ _ ]
... .List => ListItem({ "jsonrpc": "2.0", "id": CALLID, J })
requires CALLID =/=K undef
rule #sendResponse(_) ~> _ => #loadFromBatch
undef
[ _ ]
syntax KItem ::= #rpcResponseSuccess ( JSON )
| #rpcResponseSuccessException ( JSON , JSON )
| #rpcResponseError ( JSON )
| #rpcResponseError ( Int , String )
| #rpcResponseError ( Int , String , JSON )
| "#rpcResponseUnimplemented"
// --------------------------------------------
rule #rpcResponseSuccess(J) => #sendResponse( "result" : J ) ... requires isProperJson(J)
rule #rpcResponseSuccessException(RES, ERR) => #sendResponse( ( "result" : RES, "error": ERR ) ) ... requires isProperJson(RES) andBool isProperJson(ERR)
rule #rpcResponseError(ERR) => #sendResponse( "error" : ERR ) ...
rule #rpcResponseError(CODE, MSG) => #sendResponse( "error" : { "code": CODE , "message": MSG } ) ...
rule #rpcResponseError(CODE, MSG, DATA) => #sendResponse( "error" : { "code": CODE , "message": MSG , "data" : DATA } ) ... requires isProperJson(DATA)
rule #rpcResponseUnimplemented => #sendResponse( "unimplemented" : RPCCALL ) ... RPCCALL
syntax KItem ::= "#checkRPCCall"
// --------------------------------
rule #checkRPCCall => . ...
"2.0"
_:String
undef #Or [ _ ] #Or { _ }
_:String #Or null #Or _:Int #Or undef
rule #checkRPCCall => #rpcResponseError(-32600, "Invalid Request") ...
undef #Or [ _ ] #Or { _ } => null [owise]
rule #checkRPCCall => #rpcResponseError(-32600, "Invalid Request") ...
_:Int [owise]
rule #checkRPCCall => #rpcResponseError(-32600, "Invalid Request") ...
_:String [owise]
syntax KItem ::= "#runRPCCall"
// ------------------------------
rule #runRPCCall => #net_version ... "net_version"
rule #runRPCCall => #shh_version ... "shh_version"
rule #runRPCCall => #web3_clientVersion ... "web3_clientVersion"
rule #runRPCCall => #web3_sha3 ... "web3_sha3"
rule #runRPCCall => #eth_gasPrice ... "eth_gasPrice"
rule #runRPCCall => #eth_blockNumber ... "eth_blockNumber"
rule #runRPCCall => #eth_accounts ... "eth_accounts"
rule #runRPCCall => #eth_getBalance ... "eth_getBalance"
rule #runRPCCall => #eth_getStorageAt ... "eth_getStorageAt"
rule #runRPCCall => #eth_getCode ... "eth_getCode"
rule #runRPCCall => #eth_getTransactionCount ... "eth_getTransactionCount"
rule #runRPCCall => #eth_sign ... "eth_sign"
rule #runRPCCall => #eth_newBlockFilter ... "eth_newBlockFilter"
rule #runRPCCall => #eth_uninstallFilter ... "eth_uninstallFilter"
rule #runRPCCall => #eth_sendTransaction ... "eth_sendTransaction"
rule #runRPCCall => #eth_sendRawTransaction ... "eth_sendRawTransaction"
rule #runRPCCall => #eth_call ... "eth_call"
rule #runRPCCall => #eth_estimateGas ... "eth_estimateGas"
rule #runRPCCall => #eth_getTransactionReceipt ... "eth_getTransactionReceipt"
rule #runRPCCall => #eth_getBlockByNumber ... "eth_getBlockByNumber"
rule #runRPCCall => #eth_coinbase ... "eth_coinbase"
rule #runRPCCall => #eth_getBlockByHash ... "eth_getBlockByHash"
rule #runRPCCall => #eth_getBlockTransactionCountByHash ... "eth_getBlockTransactionCountByHash"
rule #runRPCCall => #eth_getBlockTransactionCountByNumber ... "eth_getBlockTransactionCountByNumber"
rule #runRPCCall => #eth_getCompilers ... "eth_getCompilers"
rule #runRPCCall => #eth_getFilterChanges ... "eth_getFilterChanges"
rule #runRPCCall => #eth_getFilterLogs ... "eth_getFilterLogs"
rule #runRPCCall => #eth_getLogs ... "eth_getLogs"
rule #runRPCCall => #eth_getTransactionByHash ... "eth_getTransactionByHash"
rule #runRPCCall => #eth_getTransactionByBlockHashAndIndex ... "eth_getTransactionByBlockHashAndIndex"
rule #runRPCCall => #eth_getTransactionByBlockNumberAndIndex ... "eth_getTransactionByBlockNumberAndIndex"
rule #runRPCCall => #eth_hashrate ... "eth_hashrate"
rule #runRPCCall => #eth_newFilter ... "eth_newFilter"
rule #runRPCCall => #eth_protocolVersion ... "eth_protocolVersion"
rule #runRPCCall => #eth_signTypedData ... "eth_signTypedData"
rule #runRPCCall => #eth_subscribe ... "eth_subscribe"
rule #runRPCCall => #eth_unsubscribe ... "eth_unsubscribe"
rule #runRPCCall => #net_peerCount ... "net_peerCount"
rule #runRPCCall => #net_listening ... "net_listening"
rule #runRPCCall => #eth_syncing ... "eth_syncing"
rule #runRPCCall => #bzz_hive ... "bzz_hive"
rule #runRPCCall => #bzz_info ... "bzz_info"
rule #runRPCCall => #evm_snapshot ... "evm_snapshot"
rule #runRPCCall => #evm_revert ... "evm_revert"
rule #runRPCCall => #evm_increaseTime ... "evm_increaseTime"
rule #runRPCCall => #evm_mine ... "evm_mine"
rule #runRPCCall => #firefly_shutdown ... "firefly_shutdown"
rule #runRPCCall => #firefly_addAccount ... "firefly_addAccount"
rule #runRPCCall => #firefly_getCoverageData ... "firefly_getCoverageData"
rule #runRPCCall => #firefly_getStateRoot ... "firefly_getStateRoot"
rule #runRPCCall => #firefly_getTxRoot ... "firefly_getTxRoot"
rule #runRPCCall => #firefly_getReceiptsRoot ... "firefly_getReceiptsRoot"
rule #runRPCCall => #firefly_getTime ... "firefly_getTime"
rule #runRPCCall => #firefly_setTime ... "firefly_setTime"
rule #runRPCCall => #firefly_genesisBlock ... "firefly_genesisBlock"
rule #runRPCCall => #firefly_setGasLimit ... "firefly_setGasLimit"
rule #runRPCCall => #firefly_blake2compress ... "firefly_blake2compress"
rule #runRPCCall => #debug_traceTransaction ... "debug_traceTransaction"
rule #runRPCCall => #miner_start ... "miner_start"
rule #runRPCCall => #miner_stop ... "miner_stop"
rule #runRPCCall => #personal_importRawKey ... "personal_importRawKey"
rule #runRPCCall => #personal_sendTransaction ... "personal_sendTransaction"
rule #runRPCCall => #personal_unlockAccount ... "personal_unlockAccount"
rule #runRPCCall => #personal_newAccount ... "personal_newAccount"
rule #runRPCCall => #personal_lockAccount ... "personal_lockAccount"
rule #runRPCCall => #personal_listAccounts ... "personal_listAccounts"
rule #runRPCCall => #rpcResponseError(-32601, "Method not found") ... [owise]
syntax KItem ::= "#firefly_shutdown"
// ------------------------------------
rule #firefly_shutdown ~> _ => #putResponse({ "jsonrpc": "2.0" , "id": CALLID , "result": "Firefly client shutting down!" }, SOCK)
true
CALLID
SOCK
_ => 0
rule #firefly_shutdown => #rpcResponseError(-32800, "Firefly client not started with `--shutdownable`!") ...
false
syntax KItem ::= "#net_version"
// -------------------------------
rule #net_version => #rpcResponseSuccess(Int2String( CID )) ...
CID
syntax KItem ::= "#web3_clientVersion"
// --------------------------------------
rule #web3_clientVersion => #rpcResponseSuccess("Firefly RPC/v0.0.1/kevm") ...
syntax KItem ::= "#eth_gasPrice"
// --------------------------------
rule #eth_gasPrice => #rpcResponseSuccess(#unparseQuantity( PRICE )) ...
PRICE
syntax KItem ::= "#eth_blockNumber"
// -----------------------------------
rule #eth_blockNumber => #rpcResponseSuccess(#unparseQuantity( BLOCKNUM )) ...
BLOCKNUM
syntax KItem ::= "#eth_accounts"
// --------------------------------
rule #eth_accounts => #rpcResponseSuccess([ #acctsToJArray( qsort(Set2List(keys(ACCTS))) ) ]) ...
ACCTS
syntax JSONs ::= #acctsToJArray ( List ) [function]
// ---------------------------------------------------
rule #acctsToJArray( .List ) => .JSONs
rule #acctsToJArray( ListItem( ACCT ) ACCTS:List ) => #unparseData( ACCT, 20 ), #acctsToJArray( ACCTS )
syntax KItem ::= "#eth_getBalance"
// ----------------------------------
rule #eth_getBalance ...
[ (DATA => #parseHexWord(DATA)), _, .JSONs ]
rule #eth_getBalance => #getAccountAtBlock(#parseBlockIdentifier(TAG), DATA) ~> #eth_getBalance ...
[ DATA, TAG, .JSONs ]
rule ... ACCTBALANCE ... ~> #eth_getBalance => #rpcResponseSuccess(#unparseQuantity( ACCTBALANCE )) ...
rule .AccountItem ~> #eth_getBalance => #rpcResponseSuccess(#unparseQuantity( 0 )) ...
rule #eth_getBalance => #rpcResponseError(-32000, "Incorrect number of arguments. Method 'eth_getBalance' requires exactly 2 arguments.") ... [owise]
syntax KItem ::= "#eth_getStorageAt"
// ------------------------------------
rule #eth_getStorageAt ...
[ (DATA => #parseHexWord(DATA)), (QUANTITY => #parseHexWord(QUANTITY)), _, .JSONs ]
rule #eth_getStorageAt => #getAccountAtBlock(#parseBlockIdentifier(TAG), DATA) ~> #eth_getStorageAt ...
[ DATA, QUANTITY, TAG, .JSONs ]
rule ... STORAGE ... ~> #eth_getStorageAt => #rpcResponseSuccess(#unparseQuantity( #lookup (STORAGE, QUANTITY) )) ...
[ DATA, QUANTITY, TAG, .JSONs ]
rule .AccountItem ~> #eth_getStorageAt => #rpcResponseSuccess(#unparseQuantity( 0 )) ...
rule #eth_getStorageAt => #rpcResponseError(-32000, "Incorrect number of arguments. Method 'eth_getStorageAt' requires exactly 3 arguments.") ... [owise]
syntax KItem ::= "#eth_getCode"
// -------------------------------
rule #eth_getCode ...
[ (DATA => #parseHexWord(DATA)), _, .JSONs ]
rule #eth_getCode => #getAccountAtBlock(#parseBlockIdentifier(TAG), DATA) ~> #eth_getCode ...
[ DATA, TAG, .JSONs ]
rule ... CODE ... ~> #eth_getCode => #rpcResponseSuccess(#unparseDataByteArray( CODE )) ...
rule .AccountItem ~> #eth_getCode => #rpcResponseSuccess(#unparseDataByteArray( .ByteArray )) ...
rule #eth_getCode => #rpcResponseError(-32000, "Incorrect number of arguments. Method 'eth_getCode' requires exactly 2 arguments.") ... [owise]
syntax KItem ::= "#eth_getTransactionCount"
// -------------------------------------------
rule #eth_getTransactionCount ...
[ (DATA => #parseHexWord(DATA)), _, .JSONs ]
rule #eth_getTransactionCount => #getAccountAtBlock(#parseBlockIdentifier(TAG), DATA) ~> #eth_getTransactionCount ...
[ DATA, TAG, .JSONs ]
rule ... NONCE ... ~> #eth_getTransactionCount => #rpcResponseSuccess(#unparseQuantity( NONCE )) ...
rule .AccountItem ~> #eth_getTransactionCount => #rpcResponseSuccess(#unparseQuantity( 0 )) ...
rule #eth_getTransactionCount => #rpcResponseError(-32000, "Incorrect number of arguments. Method 'eth_getTransactionCount' requires exactly 2 arguments.") ... [owise]
syntax KItem ::= "#eth_sign"
// ----------------------------
rule #eth_sign => #signMessage(#unparseByteStack(#asByteStack(KEY)), #hashMessage(Hex2Raw(MESSAGE))) ...
[ ACCTADDR, MESSAGE, .JSONs ]
... #parseHexWord(ACCTADDR) |-> KEY ...
rule #eth_sign => #rpcResponseError(3, "Execution error", [{ "code": 100, "message": "Account key doesn't exist, account locked!" }]) ...
[ ACCTADDR, _ ]
KEYMAP
requires notBool #parseHexWord(ACCTADDR) in_keys(KEYMAP)
syntax KItem ::= #signMessage ( String , String )
// -------------------------------------------------
rule #signMessage(KEY, MHASH) => #rpcResponseSuccess("0x" +String ECDSASign( MHASH, KEY )) ...
syntax String ::= #hashMessage ( String ) [function]
// ----------------------------------------------------
rule #hashMessage( S ) => #unparseByteStack(#parseHexBytes(Keccak256("\x19Ethereum Signed Message:\n" +String Int2String(lengthString(S)) +String S)))
syntax SnapshotItem ::= "{" BlockListCell "|" NetworkCell "|" BlockCell "|" TxReceiptsCell "}"
// ----------------------------------------------------------------------------------------------
syntax KItem ::= "#evm_snapshot"
// --------------------------------
rule #evm_snapshot => #pushNetworkState ~> #rpcResponseSuccess(#unparseQuantity( size ( SNAPSHOTS ) +Int 1 )) ...
SNAPSHOTS
syntax KItem ::= "#pushNetworkState"
// ------------------------------------
rule #pushNetworkState => . ...
... (.List => ListItem({ BLOCKLIST | NETWORK | BLOCK | RECEIPTS }))
NETWORK
BLOCK
BLOCKLIST
RECEIPTS
syntax KItem ::= "#popNetworkState"
// -----------------------------------
rule #popNetworkState => . ...
... ( ListItem({ BLOCKLIST | NETWORK | BLOCK | RECEIPTS }) => .List )
( _ => NETWORK )
( _ => BLOCK )
( _ => BLOCKLIST )
( _ => RECEIPTS )
syntax KItem ::= "#evm_revert"
// ------------------------------
rule #evm_revert => #popNetworkState ~> #rpcResponseSuccess(true) ...
[ DATA:Int, .JSONs ]
SNAPSHOTS
requires DATA ==Int ( size(SNAPSHOTS) -Int 1 )
rule #evm_revert ...
[ (DATA => #parseHexWord(DATA)), .JSONs ]
rule #evm_revert ...
( [ DATA:Int, .JSONs ] )
( SNAPSHOTS => range(SNAPSHOTS, 0, DATA ) )
requires size(SNAPSHOTS) >Int (DATA +Int 1)
rule #evm_revert => #rpcResponseError(-32000, "Incorrect number of arguments. Method 'evm_revert' requires exactly 1 arguments. Request specified 0 arguments: [null].") ...
[ .JSONs ]
rule #evm_revert => #rpcResponseSuccess(false) ... [owise]
syntax KItem ::= "#evm_increaseTime"
// ------------------------------------
rule #evm_increaseTime => #rpcResponseSuccess(Int2String(TS +Int DATA)) ...
[ DATA:Int, .JSONs ]
( TS:Int => ( TS +Int DATA ) )
syntax KItem ::= "#eth_newBlockFilter"
// --------------------------------------
rule #eth_newBlockFilter => #rpcResponseSuccess(#unparseQuantity( FILTID )) ...
( .Bag
=>
FILTID
BLOCKNUM
...
)
...
BLOCKNUM
( FILTID:Int => FILTID +Int 1 )
syntax KItem ::= "#eth_uninstallFilter"
// ---------------------------------------
rule #eth_uninstallFilter ...
[ (DATA => #parseHexWord(DATA)), .JSONs ]
rule #eth_uninstallFilter => #rpcResponseSuccess(true) ...
[ FILTID, .JSONs ]
(
FILTID
...
=> .Bag
)
...
rule #eth_uninstallFilter => #rpcResponseSuccess(false) ... [owise]
```
eth_sendTransaction
-------------------
**TODO**: Only call `#executeTx TXID` when mining is turned on, or when the mining interval comes around.
```k
syntax KItem ::= "#eth_sendTransaction"
| "#eth_sendTransaction_final"
// ---------------------------------------------
rule #eth_sendTransaction => #loadTx J ~> #eth_sendTransaction_final ...
[ ({ _ } #as J), .JSONs ]
requires isString( #getJSON("from",J) )
rule #eth_sendTransaction => #rpcResponseError(-32000, "\"from\" field not found; is required") ...
[ ({ _ } #as J), .JSONs ]
requires notBool isString( #getJSON("from",J) )
rule #eth_sendTransaction => #rpcResponseError(-32000, "Incorrect number of arguments. Method 'eth_sendTransaction' requires exactly 1 argument.") ... [owise]
rule (TXID:Int => "0x" +String #hashSignedTx(TN, TP, TG, TT, TV, TD, TW, TR, TS)) ~> #eth_sendTransaction_final ...
TXID
TN
TP
TG
TT
TV
TW
TR
TS
TD
rule TXHASH:String ~> #eth_sendTransaction_final => #rpcResponseSuccess(TXHASH) ...
EVMC_SUCCESS
rule TXHASH:String ~> #eth_sendTransaction_final => #rpcResponseSuccessException(TXHASH, #generateException(TXHASH, PCOUNT, RD))
...
EVMC_REVERT
PCOUNT
rule _:String ~> #eth_sendTransaction_final => #rpcResponseError(-32000, "base fee exceeds gas limit") ...
EVMC_OUT_OF_GAS
rule _:String ~> #eth_sendTransaction_final => #rpcResponseError(-32000, "sender doesn't have enough funds to send tx.") ...
EVMC_BALANCE_UNDERFLOW
rule _:String ~> #eth_sendTransaction_final => #rpcResponseError(-32000, "VM exception: " +String StatusCode2String( SC )) ...
SC:ExceptionalStatusCode [owise]
rule loadTransaction _ { "gas" : (TG:String => #parseHexWord(TG)), _ } ...
rule loadTransaction _ { "gasPrice" : (TP:String => #parseHexWord(TP)), _ } ...
rule loadTransaction _ { "nonce" : (TN:String => #parseHexWord(TN)), _ } ...
rule loadTransaction _ { "v" : (TW:String => #parseHexWord(TW)), _ } ...
rule loadTransaction _ { "value" : (TV:String => #parseHexWord(TV)), _ } ...
rule loadTransaction _ { "to" : (TT:String => #parseHexWord(TT)), _ } ...
rule loadTransaction _ { "data" : (TI:String => #parseByteStack(TI)), _ } ...
rule loadTransaction _ { "r" : (TR:String => #padToWidth(32, #parseByteStack(TR))), _ } ...
rule loadTransaction _ { "s" : (TS:String => #padToWidth(32, #parseByteStack(TS))), _ } ...
rule loadTransaction _ { ("from" : _, REST => REST) } ...
syntax KItem ::= "#loadNonce" Int Int
// -------------------------------------
rule #loadNonce ACCT TXID => . ...
TXID
_ => NONCE
...
ACCT
NONCE
...
syntax JSON ::= #generateException( String, Int, Bytes ) [function]
// -------------------------------------------------------------------
rule #generateException(TXHASH, PCOUNT, RD) => { "message": "VM Exception while processing transaction: revert",
"code": -32000,
"data": {
TXHASH: {
"error": "revert",
"program_counter": PCOUNT +Int 1,
"return": #unparseDataByteArray( RD ),
"reason": Bytes2String(substrBytes(RD,
36 +Int #asInteger(substrBytes(RD,5,36)),
36 +Int #asInteger(substrBytes(RD,5,36)) +Int #asInteger(substrBytes(RD,37,68))))
}
}
}
requires lengthBytes(RD) >Int 68
rule #generateException(TXHASH, PCOUNT, RD) => { "message": "VM Exception while processing transaction: revert",
"code": -32000,
"data": {
TXHASH: {
"error": "revert",
"program_counter": PCOUNT +Int 1,
"return": #unparseDataByteArray( RD )
}
}
}
requires notBool lengthBytes(RD) >Int 68
```
- signTX TXID ACCTFROM: Signs the transaction with TXID using ACCTFROM's private key
```k
syntax KItem ::= "signTX" Int Int
| "signTX" Int String [klabel(signTXAux)]
// --------------------------------------------------------
rule signTX TXID ACCTFROM:Int => signTX TXID ECDSASign( Hex2Raw( #hashUnsignedTx(TN, TP, TG, TT, TV, TD) ), #unparseByteStack( #padToWidth( 32, #asByteStack( KEY ) ) ) ) ...
... ACCTFROM |-> KEY ...
NORMAL
TXID
TN
TP
TG
TT
TV
TD
...
rule signTX TXID ACCTFROM:Int => signTX TXID ECDSASign( Hex2Raw( #hashUnsignedTx(TN, TP, TG, TT, TV, TD) ), #unparseByteStack( ( #padToWidth( 20, #asByteStack( ACCTFROM ) ) ++ #padToWidth( 20, #asByteStack( ACCTFROM ) ) )[0 .. 32] ) ) ...
NOGAS
TXID
TN
TP
TG
TT
TV
TD
...
rule signTX TXID SIG:String => . ...
TXID
_ => #parseHexBytes( substrString( SIG, 0, 64 ) )
_ => #parseHexBytes( substrString( SIG, 64, 128 ) )
_ => #parseHexWord( substrString( SIG, 128, 130 ) ) +Int 27
...
```
eth_sendRawTransaction
----------------------
**TODO**: Verify the signature provided for the transaction
```k
syntax KItem ::= "#eth_sendRawTransaction"
| "#eth_sendRawTransactionLoad"
| "#eth_sendRawTransactionVerify" Int
| "#eth_sendRawTransactionSend" Int
// ----------------------------------------------------
rule #eth_sendRawTransaction => #eth_sendRawTransactionLoad ...
[ RAWTX:String, .JSONs ] => #rlpDecode( Hex2Raw( RAWTX ) )
rule #eth_sendRawTransaction => #rpcResponseError(-32000, "\"value\" argument must not be a number") ...
[ _:Int, .JSONs ]
rule #eth_sendRawTransaction => #rpcResponseError(-32000, "Invalid Signature") ... [owise]
rule #eth_sendRawTransactionLoad
=> mkTX !ID:Int
~> loadTransaction !ID { "data" : Raw2Hex(TI) , "gas" : Raw2Hex(TG) , "gasPrice" : Raw2Hex(TP)
, "nonce" : Raw2Hex(TN) , "r" : Raw2Hex(TR) , "s" : Raw2Hex(TS)
, "to" : Raw2Hex(TT) , "v" : Raw2Hex(TW) , "value" : Raw2Hex(TV)
, .JSONs
}
~> #eth_sendRawTransactionVerify !ID
...
[ TN, TP, TG, TT, TV, TI, TW, TR, TS, .JSONs ]
rule #eth_sendRawTransactionLoad => #rpcResponseError(-32000, "Invalid Signature") ... [owise]
rule #eth_sendRawTransactionVerify TXID => #eth_sendRawTransactionSend TXID ...
TXID
TN
TP
TG
TT
TV
TD
TW
TR
TS
requires ECDSARecover( Hex2Raw( #hashUnsignedTx(TN, TP, TG, TT, TV, TD) ), TW, #unparseByteStack(TR), #unparseByteStack(TS) ) =/=String ""
rule #eth_sendRawTransactionVerify _ => #rpcResponseError(-32000, "Invalid Signature") ... [owise]
rule #eth_sendRawTransactionSend TXID => #rpcResponseSuccess("0x" +String #hashSignedTx(TN, TP, TG, TT, TV, TD, TW, TR, TS)) ...
TXID
TN
TP
TG
TT
TV
TD
TW
TR
TS
```
Retrieving Blocks
-----------------
**TODO**
- defaults to .ByteArray, but maybe it should be 256 zero bytes? It also doesn't get updated.
- Ganache's gasLimit defaults to 6721975 (0x6691b7), but we default it at 0.
- After each txExecution which is not `eth_call`:
- use `#setBlockchainItem`
- clear and
- Some initialization still needs to be done, like the trie roots and the 0 block in
- I foresee issues with firefly_addAccount and personal_importRawKey if we want those accounts
in the stateRoot of the initial block
```k
syntax KItem ::= "#eth_getBlockByNumber"
// ----------------------------------------
rule #eth_getBlockByNumber => #eth_getBlockByNumber_finalize( #getBlockByNumber(#parseBlockIdentifier(TAG), BLOCKLIST, { NETWORK | BLOCK })) ...
[ TAG:String, TXOUT:Bool, .JSONs ]
BLOCKLIST
NETWORK
BLOCK
rule #eth_getBlockByNumber => #rpcResponseError(-32000, "Incorrect number of arguments. Method 'eth_getBlockByNumber' requires exactly 2 arguments.") ...
[ VALUE, .JSONs ]
requires notBool isJSONs( VALUE )
rule #eth_getBlockByNumber => #rpcResponseError(-32000, "Incorrect number of arguments. Method 'eth_getBlockByNumber' requires exactly 2 arguments.") ...
[ VALUE, VALUE2, _, .JSONs ]
requires notBool isJSONs( VALUE ) andBool notBool isJSONs( VALUE2 )
syntax KItem ::= "#eth_getBlockByNumber_finalize" "(" BlockchainItem ")"
// ------------------------------------------------------------------------
rule #eth_getBlockByNumber_finalize ({ _ |
PARENTHASH
OMMERSHASH
MINER
STATEROOT
TXROOT
RCPTROOT
LOGSBLOOM //#bloomFilter( LOGS >)
DFFCLTY
NUM
GLIMIT
GUSED
TIME
DATA
MIXHASH
NONCE
...
} #as BLOCKITEM)
=> #rpcResponseSuccess( { "number": #unparseQuantity( NUM )
, "hash": "0x" +String Keccak256( #rlpEncodeBlock( BLOCKITEM ) )
, "parentHash": #unparseData( PARENTHASH, 32 )
, "mixHash": #unparseData( MIXHASH, 32 )
, "nonce": #unparseData( NONCE, 8 )
, "sha3Uncles": #unparseData( OMMERSHASH, 32 )
, "logsBloom": #unparseDataByteArray( LOGSBLOOM )
, "transactionsRoot": #unparseData( TXROOT, 32)
, "stateRoot": #unparseData( STATEROOT, 32)
, "receiptsRoot": #unparseData( RCPTROOT, 32)
, "miner": #unparseData( MINER, 20 )
, "difficulty": #unparseQuantity( DFFCLTY )
, "totalDifficulty": #unparseQuantity( DFFCLTY )
, "extraData": #unparseDataByteArray( DATA )
, "size": "0x3e8" // Ganache always returns 1000
, "gasLimit": #unparseQuantity( GLIMIT )
, "gasUsed": #unparseQuantity( GUSED )
, "timestamp": #unparseQuantity( TIME )
, "transactions": [ #getTransactionList( BLOCKITEM, TXRECEIPTS , FULLTX ) ]
, "uncles": [ .JSONs ]
}
)
...
[ _, FULLTX:Bool, .JSONs ]
TXRECEIPTS
rule #eth_getBlockByNumber_finalize ( .BlockchainItem )=> #rpcResponseSuccess(null) ...
syntax JSONs ::= #getTransactionList ( BlockchainItem , TxReceiptsCell , Bool ) [function]
| #getTransactionHashList ( List, TxReceiptsCell , JSONs ) [function]
// ------------------------------------------------------------------------------------------
rule #getTransactionList ( { TXIDLIST ... | _ } , TXRECEIPTS , false )
=> #getTransactionHashList (TXIDLIST, TXRECEIPTS , .JSONs)
rule #getTransactionHashList ( .List, _, RESULT ) => RESULT
rule #getTransactionHashList ( ( ListItem(TXID) => .List ) TXIDLIST
,
TXID
TXHASH
...
...
, ( RESULT => TXHASH, RESULT ) )
```
Transaction Receipts
--------------------
- The transaction receipt is a tuple of four items comprising:
- the cumulative gas used in the block containing the transaction receipt as of immediately after the transaction has happened,
- the set of logs created through execution of the transaction,
- the Bloom filter composed from information in those logs, and
- the status code of the transaction.
```k
syntax KItem ::= "#makeTxReceipt" Int
// -------------------------------------
rule #makeTxReceipt TXID => . ...
( .Bag
=>
"0x" +String #hashSignedTx(TN, TP, TG, TT, TV, TD, TW, TR, TS)
CGAS
LOGS
#bloomFilter(LOGS)
bool2Word(STATUSCODE ==K EVMC_SUCCESS)
TXID
#parseHexWord(#unparseDataByteArray(#ecrecAddr(#sender(TN, TP, TG, TT, TV, #unparseByteStack(TD), TW , TR, TS))))
BN +Int 1
)
...
TXID
TN
TP
TG
TT
TV
TW
TR
TS
TD
STATUSCODE
CGAS
LOGS
BN
syntax KItem ::= "#eth_getTransactionReceipt"
| "#eth_getTransactionReceipt_final" "(" BlockchainItem ")"
// --------------------------------------------------------------------------
rule #eth_getTransactionReceipt => #eth_getTransactionReceipt_final(#getBlockByNumber (BN, BLOCKLIST, { NETWORK | BLOCK })) ...
[TXHASH:String, .JSONs]
TXHASH
BN
...
BLOCKLIST
NETWORK
BLOCK
rule #eth_getTransactionReceipt_final ({
TXLIST
TXID
TN
TT:Account
TW
TR
TS
...
TXFROM
NONCE
...
...
| _ } #as BLOCKITEM )
=> #rpcResponseSuccess( { "transactionHash": TXHASH
, "transactionIndex": #unparseQuantity(getIndexOf(TXID, TXLIST))
, "blockHash": "0x" +String Keccak256(#rlpEncodeBlock(BLOCKITEM))
, "blockNumber": #unparseQuantity(BN)
, "from": #unparseAccount(TXFROM)
, "to": #unparseAccount(TT)
, "gasUsed": #unparseQuantity(CGAS)
, "cumulativeGasUsed": #unparseQuantity(CGAS)
, "contractAddress": #if TT ==K .Account #then #unparseData(#newAddr(TXFROM, NONCE -Int 1), 20) #else null #fi
, "logs": [#serializeLogs(LOGS, 0, getIndexOf(TXID, TXLIST), TXHASH, "0x" +String Keccak256(#rlpEncodeBlock(BLOCKITEM)), BN)]
, "status": #unparseQuantity(TXSTATUS)
, "logsBloom": #unparseDataByteArray(BLOOM)
, "v": #unparseQuantity(TW)
, "r": #unparseQuantity( #asWord(TR) )
, "s": #unparseQuantity( #asWord(TS) )
}
)
...
[TXHASH:String, .JSONs]
TXHASH
TXID
CGAS
LOGS
BLOOM
TXSTATUS
TXFROM
BN
rule #eth_getTransactionReceipt => #rpcResponseSuccess(null) ... [owise]
syntax Int ::= getIndexOf ( Int, List ) [function]
// --------------------------------------------------
rule getIndexOf(X:Int, L) => getIndexOfAux(X:Int, L, 0)
syntax Int ::= getIndexOfAux (Int, List, Int) [function]
// --------------------------------------------------------
rule getIndexOfAux (X:Int, .List, _:Int) => -1
rule getIndexOfAux (X:Int, ListItem(X) L, INDEX) => INDEX
rule getIndexOfAux (X:Int, ListItem(I) L, INDEX) => getIndexOfAux(X, L, INDEX +Int 1) requires X =/=Int I
syntax JSON ::= #unparseAccount ( Account ) [function]
// ------------------------------------------------------
rule #unparseAccount (.Account) => null
rule #unparseAccount (ACCT:Int) => #unparseData(ACCT, 20)
syntax JSONs ::= #unparseIntList ( List ) [function]
// ----------------------------------------------------
rule #unparseIntList (L) => #unparseIntListAux( L, .JSONs)
syntax JSONs ::= #unparseIntListAux ( List, JSONs ) [function]
// --------------------------------------------------------------
rule #unparseIntListAux(.List, RESULT) => RESULT
rule #unparseIntListAux(L ListItem(I), RESULT) => #unparseIntListAux(L, (#unparseDataByteArray(#padToWidth(32,#asByteStack(I))), RESULT))
syntax JSONs ::= #serializeLogs ( List, Int, Int, String, String, Int ) [function]
// ----------------------------------------------------------------------------------
rule #serializeLogs (.List, _, _, _, _, _) => .JSONs
rule #serializeLogs (ListItem({ ACCT | TOPICS:List | DATA }) L, LI, TI, TH, BH, BN) => {
"logIndex": #unparseQuantity(LI),
"transactionIndex": #unparseQuantity(TI),
"transactionHash": TH,
"blockHash": BH,
"blockNumber": #unparseQuantity(BN),
"address": #unparseData(ACCT, 20),
"data": #unparseDataByteArray(DATA),
"topics": [#unparseIntList(TOPICS)],
"type" : "mined"
}, #serializeLogs(L, LI +Int 1, TI, TH, BH, BN)
```
- loadCallState: web3.md specific rules
```k
rule loadCallState { "from" : ( ACCTFROM:String => #parseHexWord( ACCTFROM ) ), REST } ...
rule loadCallState { "to" : ( ACCTTO:String => #parseHexWord( ACCTTO ) ), REST } ...
rule loadCallState { "gas" : ( GLIMIT:String => #parseHexWord( GLIMIT ) ), REST } ...
rule loadCallState { "gasPrice" : ( GPRICE:String => #parseHexWord( GPRICE ) ), REST } ...
rule loadCallState { "value" : ( VALUE:String => #parseHexWord( VALUE ) ), REST } ...
rule loadCallState { "nonce" : _, REST => REST } ...
rule loadCallState { "from" : ACCTFROM:Int, REST => REST } ...
_ => ACCTFROM
_ => ACCTFROM
rule loadCallState { "to" : .Account , REST => REST } ...
rule loadCallState { ("to" : ACCTTO:Int => "code" : CODE), REST } ...
_ => ACCTTO
ACCTTO
CODE
...
rule ( . => #newAccount ACCTTO ) ~> loadCallState { "to" : ACCTTO:Int, REST } ... [owise]
rule loadCallState TXID:Int
=> loadCallState {
"from": #unparseDataByteArray(#ecrecAddr(#sender(TN, TP, TG, TT, TV, #unparseByteStack(DATA), TW , TR, TS))),
"to": TT,
"gas": TG,
"gasPrice": TP,
"value": TV,
"data": DATA
}
...
TXID
TN
TP
TG
TT
TV
TW
TR
TS
DATA
syntax ByteArray ::= #ecrecAddr ( Account ) [function]
// ------------------------------------------------------
rule #ecrecAddr(.Account) => .ByteArray
rule #ecrecAddr(N:Int) => #padToWidth(20, #asByteStack(N))
```
Transaction Execution
---------------------
- `#executeTx` takes a transaction, loads it into the current state and executes it.
**TODO**: treat the account creation case
**TODO**: record the logs after `finalizeTX`
**TODO**: execute all pending transactions
```k
syntax KItem ::= "#loadTx" JSON
// -------------------------------
rule #loadTx J
=> mkTX !ID:Int
~> #loadNonce #parseHexWord(#getString("from", J)) !ID
~> loadTransaction !ID J
~> signTX !ID #parseHexWord(#getString("from", J))
~> #prepareTx !ID #parseHexWord(#getString("from", J))
~> !ID
...
syntax KItem ::= "#prepareTx" Int Account
// -----------------------------------------
rule #prepareTx TXID:Int ACCTFROM
=> #clearLogs
~> #validateTx TXID
...
_ => ACCTFROM
syntax KItem ::= "#validateTx" Int
// ----------------------------------
rule #validateTx TXID => . ...
( _ => EVMC_OUT_OF_GAS)
SCHED
TXID
GLIMIT
DATA
ACCTTO
...
requires ( GLIMIT -Int G0(SCHED, DATA, (ACCTTO ==K .Account)) ) #validateTx TXID => #executeTx TXID ~> #makeTxReceipt TXID ~> #finishTx ...
SCHED
_ => GLIMIT -Int G0(SCHED, DATA, (ACCTTO ==K .Account) )
TXID
GLIMIT
DATA
ACCTTO
...
requires ( GLIMIT -Int G0(SCHED, DATA, (ACCTTO ==K .Account)) ) >=Int 0
syntax KItem ::= "#executeTx" Int
// ---------------------------------
rule #executeTx TXID:Int
=> #create ACCTFROM #newAddr(ACCTFROM, NONCE) VALUE CODE
~> #catchHaltTx #newAddr(ACCTFROM, NONCE)
~> #finalizeTx(false)
...
_ => GPRICE
ACCTFROM
_ => -1
ListItem(TXID:Int) ...
MINER
TXID
GPRICE
GLIMIT
.Account
VALUE
CODE
...
ACCTFROM
BAL => BAL -Int (GLIMIT *Int GPRICE)
NONCE
...
_ => SetItem(MINER)
rule #executeTx TXID:Int
=> #call ACCTFROM ACCTTO ACCTTO VALUE VALUE DATA false
~> #catchHaltTx .Account
~> #finalizeTx(false)
...
ACCTFROM
_ => GPRICE
ListItem(TXID) ...
_ => -1
MINER
TXID
GPRICE
GLIMIT
ACCTTO
VALUE
DATA
...
ACCTFROM
BAL => BAL -Int (GLIMIT *Int GPRICE)
NONCE => NONCE +Int 1
...
_ => SetItem(MINER)
requires ACCTTO =/=K .Account
syntax KItem ::= "#finishTx"
// ----------------------------
rule STATUSCODE
#finishTx => #mineBlock ...
EXECMODE
requires EXECMODE =/=K NOGAS
andBool ( STATUSCODE ==K EVMC_SUCCESS orBool STATUSCODE ==K EVMC_REVERT )
rule #finishTx => #clearGas ... [owise]
syntax KItem ::= "#catchHaltTx" Account
// ---------------------------------------
rule _:ExceptionalStatusCode
#halt ~> #catchHaltTx _ => #popCallStack ~> #popWorldState ...
rule EVMC_REVERT
#halt ~> #catchHaltTx _ => #popCallStack ~> #popWorldState ~> #refund GAVAIL ...
PCOUNT
GAVAIL
_ => PCOUNT
rule EVMC_SUCCESS
#halt ~> #catchHaltTx .Account => . ...
rule EVMC_SUCCESS
#halt ~> #catchHaltTx ACCT => #mkCodeDeposit ACCT ...
requires ACCT =/=K .Account
syntax KItem ::= "#clearLogs"
// -----------------------------
rule #clearLogs => . ...
_ => .List
```
- `#personal_importRawKey` Takes an unencrypted private key, encrypts it with a passphrase, stores it and returns the address of the key.
**TODO**: Currently nothing is done with the passphrase
```k
syntax KItem ::= "#personal_importRawKey"
// -----------------------------------------
rule #personal_importRawKey => #acctFromPrivateKey PRIKEY ~> #rpcResponseSuccess(#unparseData( #addrFromPrivateKey( PRIKEY ), 20 )) ...
[ PRIKEY:String, PASSPHRASE:String, .JSONs ]
requires lengthString( PRIKEY ) ==Int 66
rule #personal_importRawKey => #rpcResponseError(-32000, "Private key length is invalid. Must be 32 bytes.") ...
[ PRIKEY:String, _:String, .JSONs ]
requires lengthString( PRIKEY ) =/=Int 66
rule #personal_importRawKey => #rpcResponseError(-32000, "Method 'personal_importRawKey' requires exactly 2 parameters") ... [owise]
syntax KItem ::= "#acctFromPrivateKey" String
// ---------------------------------------------
rule #acctFromPrivateKey KEY => #newAccount #addrFromPrivateKey(KEY) ...
M => M[#addrFromPrivateKey(KEY) <- #parseHexWord(KEY)]
syntax KItem ::= "#firefly_addAccount" | "#firefly_addAccountByAddress" Int | "#firefly_addAccountByKey" String
// ---------------------------------------------------------------------------------------------------------------
rule #firefly_addAccount => #firefly_addAccountByAddress #parseHexWord(#getString("address", J)) ...
[ ({ _ } #as J), .JSONs ]
requires isString(#getJSON("address", J))
rule #firefly_addAccount => #firefly_addAccountByKey #getString("key", J) ...
[ ({ _ } #as J), .JSONs ]
requires isString(#getJSON("key", J))
rule #firefly_addAccountByAddress ACCT_ADDR => #newAccount ACCT_ADDR ~> loadAccount ACCT_ADDR J ~> #rpcResponseSuccess(true) ...
[ ({ _ } #as J), .JSONs ]
ACCTS
requires notBool ACCT_ADDR in ACCTS
rule #firefly_addAccountByAddress ACCT_ADDR => #rpcResponseSuccess(false) ...
[ ({ _ } #as J), .JSONs ]
ACCTS
requires ACCT_ADDR in ACCTS
rule #firefly_addAccountByKey ACCT_KEY => #acctFromPrivateKey ACCT_KEY ~> loadAccount #addrFromPrivateKey(ACCT_KEY) J ~> #rpcResponseSuccess(true) ...
[ ({ _ } #as J), .JSONs ]
ACCTS
requires notBool #addrFromPrivateKey(ACCT_KEY) in ACCTS
rule #firefly_addAccountByKey ACCT_KEY => #rpcResponseSuccess(false) ...
[ ({ _ } #as J), .JSONs ]
ACCTS
requires #addrFromPrivateKey(ACCT_KEY) in ACCTS
rule #firefly_addAccount => #rpcResponseError(-32025, "Method 'firefly_addAccount' has invalid arguments") ... [owise]
rule loadAccount _ { "balance" : ((VAL:String) => #parseHexWord(VAL)), _ } ...
rule loadAccount _ { "nonce" : ((VAL:String) => #parseHexWord(VAL)), _ } ...
rule loadAccount _ { "code" : ((CODE:String) => #parseByteStack(CODE)), _ } ...
rule loadAccount _ { "storage" : ({ STORAGE:JSONs } => #parseMap({ STORAGE })), _ } ...
rule loadAccount _ { "key" : _, REST => REST } ...
rule loadAccount _ { "address" : _, REST => REST } ...
```
- `#eth_call`
**TODO**: add logic for the case in which "from" field is not present
```k
syntax KItem ::= "#eth_call"
// ----------------------------
rule #eth_call ...
[ { _ }, (.JSONs => "pending", .JSONs) ]
rule #eth_call
=> #pushNetworkState
~> #setMode NOGAS
~> #loadTx J
~> #eth_call_finalize
...
[ ({ _ } #as J), TAG, .JSONs ]
requires isString( #getJSON("from" , J) )
rule #eth_call => #rpcResponseError(-32027, "Method 'eth_call' has invalid arguments") ...
[ ({ _ } #as J), TAG, .JSONs ]
requires notBool isString( #getJSON("from", J) )
syntax KItem ::= "#eth_call_finalize"
// -------------------------------------
rule EVMC_SUCCESS
_:Int ~> #eth_call_finalize
=> #setMode NORMAL
~> #popNetworkState
~> #clearGas
~> #rpcResponseSuccess(#unparseDataByteArray( OUTPUT ))
...
rule EVMC_REVERT
TXID:Int ~> #eth_call_finalize
=> #setMode NORMAL
~> #popNetworkState
~> #clearGas
~> #rpcResponseError(#generateException("0x" +String #hashSignedTx(TN, TP, TG, TT, TV, TD, TW, TR, TS),
PCOUNT, RD))
...
PCOUNT
TXID
TN
TP
TG
TT
TV
TW
TR
TS
TD
```
- `#eth_estimateGas`
**TODO**: add test for EVMC_OUT_OF_GAS
**TODO**: implement funcionality for block number argument
```k
syntax KItem ::= "#eth_estimateGas"
// -----------------------------------
rule #eth_estimateGas ...
[ { _ }, (.JSONs => "pending", .JSONs) ]
rule #eth_estimateGas
=> #pushNetworkState
~> #loadTx J
~> #eth_estimateGas_finalize GUSED
...
[ ({ _ } #as J), TAG, .JSONs ]
GUSED
requires isString(#getJSON("from", J) )
rule #eth_estimateGas => #rpcResponseError(-32028, "Method 'eth_estimateGas' has invalid arguments") ...
[ ({ _ } #as J), TAG, .JSONs ]
requires notBool isString( #getJSON("from", J) )
syntax KItem ::= "#eth_estimateGas_finalize" Int
// ------------------------------------------------
rule _:Int ~> #eth_estimateGas_finalize INITGUSED:Int => #popNetworkState ~> #rpcResponseSuccess(#unparseQuantity( #getGasUsed( #getBlockByNumber(LATEST, BLOCKLIST, { NETWORK | BLOCK }) ) -Int INITGUSED )) ...
STATUSCODE
BLOCKLIST
NETWORK
BLOCK
requires STATUSCODE =/=K EVMC_OUT_OF_GAS
rule _:Int ~> #eth_estimateGas_finalize _ => #popNetworkState ~> #rpcResponseError(-32000 , "base fee exceeds gas limit") ...
EVMC_OUT_OF_GAS
syntax Int ::= #getGasUsed( BlockchainItem ) [function]
// -------------------------------------------------------
rule #getGasUsed( { _ | GUSED ... } ) => GUSED
```
NOGAS Mode
----------
- Used for `eth_call` RPC messages
```k
syntax Mode ::= "NOGAS"
// -----------------------
rule #gas [ OP , AOP ] => . ...
NOGAS
[priority(25)]
rule #validateTx TXID => #executeTx TXID ~> #makeTxReceipt TXID ~> #finishTx ...
NOGAS
[priority(25)]
```
Collecting Coverage Data
------------------------
- `` cell is used to differentiate between the generated code used for contract deployment and the bytecode of the contract.
- `` cell is a map which stores the program counters which were hit during the execution of a program. The key, named `CoverageIdentifier`, contains the hash of the bytecode which is executed, and the phase of the execution.
- `` cell is a map similar to `` which stores instead a list containing all the `OpcodeItem`s of the executed bytecode for each contract.
- `OpcodeItem` is a tuple which contains the Program Counter, the Opcode name, and the opcode's argument.
**TODO**: compute coverage percentages in `Float` instead of `Int`
```k
syntax Phase ::= ".Phase"
| "CONSTRUCTOR"
| "RUNTIME"
syntax CoverageIdentifier ::= "{" Int "|" Phase "}"
rule #mkCall _ _ _ _ _ _ _ ...
( EPHASE => RUNTIME )
requires EPHASE =/=K RUNTIME
[priority(25)]
rule #mkCreate _ _ _ _ ...
( EPHASE => CONSTRUCTOR )
requires EPHASE =/=K CONSTRUCTOR
[priority(25)]
rule #initVM ...
OC => OC [ {keccak(PGM) | EPHASE} <- .Map ]
EPHASE
PGM
requires notBool {keccak(PGM) | EPHASE} in_keys(OC)
[priority(25)]
rule #initVM ...
OL => OL [ {keccak(PGM) | EPHASE} <- #parseByteCode(PGM,SCHED) ]
EPHASE
SCHED
PGM
requires notBool {keccak(PGM) | EPHASE} in_keys(OL)
[priority(25)]
syntax OpcodeItem ::= "{" Int "|" OpCode "|" String "}"
syntax List ::= #parseByteCode( ByteArray, Schedule ) [function]
// ----------------------------------------------------------------
rule #parseByteCode(PGM , SCHED) => #parseByteCodeAux(0, #sizeByteArray(PGM), PGM, SCHED, .List)
syntax List ::= #parseByteCodeAux ( Int, Int, ByteArray, Schedule, List ) [function]
// ------------------------------------------------------------------------------------
rule #parseByteCodeAux(PCOUNT, SIZE, _, _, OPLIST) => OPLIST
requires PCOUNT >=Int SIZE
rule #parseByteCodeAux(PCOUNT, SIZE, PGM, SCHED, OPLIST) => #parseByteCodeAux(PCOUNT +Int #widthOp(#dasmOpCode(PGM [ PCOUNT ], SCHED)), SIZE, PGM, SCHED, OPLIST ListItem({ PCOUNT | #dasmOpCode(PGM [ PCOUNT ], SCHED) | #unparseDataByteArray(substrBytes(PGM, PCOUNT +Int 1, PCOUNT +Int #widthOp(#dasmOpCode(PGM [ PCOUNT ], SCHED))))} ) )
requires PCOUNT #execute ...
PCOUNT
EPHASE
PGM
... { keccak(PGM) | EPHASE } |-> (PCS => PCS (PCOUNT |-> 1) ) ...
_ => PCOUNT
requires notBool PCOUNT in_keys(PCS)
[priority(25)]
rule #execute ...
PCOUNT
EPHASE
PGM
... { keccak(PGM) | EPHASE } |-> ((PCS (PCOUNT |-> HC)) => ((PCOUNT |-> (HC +Int 1)) PCS)) ...
PREV => PCOUNT
requires PCOUNT =/=Int PREV
[priority(25)]
syntax KItem ::= "#firefly_getCoverageData"
// -------------------------------------------
rule #firefly_getCoverageData => #rpcResponseSuccess([#makeCoverageReport(keys_list(PGMS),COVERAGE, PGMS)]) ...
COVERAGE
PGMS
syntax JSONs ::= #makeCoverageReport ( List, Map, Map ) [function]
// ------------------------------------------------------------------
rule #makeCoverageReport (.List , _ , _ ) => .JSONs
rule #makeCoverageReport ((ListItem({ CODEHASH | EPHASE } #as KEY) KEYS), COVERAGE, PGMS) => {
"hash": Int2String(CODEHASH),
"programName": Phase2String(EPHASE),
"coverage": #computePercentage(size({COVERAGE[KEY]}:>Map), size({PGMS[KEY]}:>List)),
"program": [#serializePrograms({PGMS[KEY]}:>List, {COVERAGE[KEY]}:>Map)],
"coveredOpCodes": [#serializeCoverage(KEY, COVERAGE)]
}, #makeCoverageReport(KEYS, COVERAGE, PGMS)
syntax JSONs ::= #serializeCoverage ( CoverageIdentifier, Map ) [function]
// --------------------------------------------------------------------------
rule #serializeCoverage (KEY, COVERAGE ) => .JSONs requires notBool KEY in_keys(COVERAGE)
rule #serializeCoverage (KEY, KEY |-> X:Map COVERAGE:Map ) => IntList2JSONs(qsort(keys_list(X)))
syntax JSONs ::= #serializePrograms ( List, Map ) [function]
// ------------------------------------------------------------
rule #serializePrograms (.List , _ ) => .JSONs
rule #serializePrograms (ListItem({PCOUNT:Int | OPC:OpCode | V:String}) PROGRAM, COVERAGE) => {
"programCounter": PCOUNT,
"hitCount": {COVERAGE[PCOUNT] orDefault 0}:>Int,
"opcode": #opcode2String(OPC),
"argument": #if V =/=String "0x" #then V #else null #fi
}, #serializePrograms(PROGRAM, COVERAGE)
syntax String ::= Phase2String ( Phase ) [function]
// ---------------------------------------------------
rule Phase2String (CONSTRUCTOR) => "CONSTRUCTOR"
rule Phase2String (RUNTIME) => "RUNTIME"
syntax JSONs ::= IntList2JSONs ( List ) [function]
// --------------------------------------------------
rule IntList2JSONs (.List) => .JSONs
rule IntList2JSONs (ListItem(I:Int) L) => I, IntList2JSONs(L)
syntax List ::= getIntElementsSmallerThan ( Int, List, List ) [function]
// ------------------------------------------------------------------------
rule getIntElementsSmallerThan (_, .List, RESULTS) => RESULTS
rule getIntElementsSmallerThan (X, (ListItem(I:Int) L), RESULTS) => getIntElementsSmallerThan (X, L, ListItem(I) RESULTS) requires I getIntElementsSmallerThan (X, L, RESULTS) requires I >=Int X
syntax List ::= getIntElementsGreaterThan ( Int, List, List ) [function]
// ------------------------------------------------------------------------
rule getIntElementsGreaterThan (_, .List , RESULTS) => RESULTS
rule getIntElementsGreaterThan (X, (ListItem(I:Int) L), RESULTS) => getIntElementsGreaterThan (X, L, ListItem(I) RESULTS) requires I >Int X
rule getIntElementsGreaterThan (X, (ListItem(I:Int) L), RESULTS) => getIntElementsGreaterThan (X, L, RESULTS) requires I <=Int X
syntax List ::= qsort ( List ) [function]
// -----------------------------------------
rule qsort ( .List ) => .List
rule qsort (ListItem(I:Int) L) => qsort(getIntElementsSmallerThan(I, L, .List)) ListItem(I) qsort(getIntElementsGreaterThan(I, L, .List))
syntax Int ::= #computePercentage ( Int, Int ) [function]
// ---------------------------------------------------------
rule #computePercentage (EXECUTED, TOTAL) => (100 *Int EXECUTED) /Int TOTAL
```
Helper Funcs
------------
```k
syntax String ::= #rlpEncodeBlock( BlockchainItem ) [function]
// --------------------------------------------------------------
rule #rlpEncodeBlock( { _ |
PARENTHASH
OMMERSHASH
MINER
STATEROOT
TXROOT
RCPTROOT
LOGSBLOOM
DFFCLTY
NUM
GLIMIT
GUSED
TIME
DATA
MIXHASH
NONCE
...
} )
=> #rlpEncodeLength( #rlpEncodeBytes( PARENTHASH, 32 )
+String #rlpEncodeBytes( OMMERSHASH, 32 )
+String #rlpEncodeBytes( MINER, 20 )
+String #rlpEncodeBytes( STATEROOT, 32 )
+String #rlpEncodeBytes( TXROOT, 32 )
+String #rlpEncodeBytes( RCPTROOT, 32 )
+String #rlpEncodeBytes( #asInteger( LOGSBLOOM ), 256 )
+String #rlpEncodeWord ( DFFCLTY )
+String #rlpEncodeWord ( NUM )
+String #rlpEncodeWord ( GLIMIT )
+String #rlpEncodeWord ( GUSED )
+String #rlpEncodeWord ( TIME )
+String #rlpEncodeBytes( #asInteger( DATA ), #sizeByteArray( DATA ) )
+String #rlpEncodeBytes( MIXHASH, 32 )
+String #rlpEncodeBytes( NONCE, 8 )
, 192
)
```
State Root
----------
```k
syntax MerkleTree ::= #stateRoot ( NetworkCell ) [function]
| #stateRootAux( MerkleTree, Set, AccountsCell ) [function]
// -------------------------------------------------------------------------------
rule #stateRoot(
ACCTS
ACCTSCELL
...
)
=> #stateRootAux( MerkleUpdateMap( .MerkleTree, #precompiledContracts ), ACCTS, ACCTSCELL )
rule #stateRootAux( (TREE => MerkleUpdate( TREE, Hex2Raw( #unparseData(ACCT,20) ), #rlpEncodeFullAccount(NONCE, BAL, STORAGE, CODE) ))
, (SetItem(ACCT) => .Set) ACCTS
,
ACCT
NONCE
BAL
STORAGE
CODE
...
...
)
rule #stateRootAux( TREE, .Set, _ ) => TREE
syntax KItem ::= "#firefly_getStateRoot"
// ----------------------------------------
rule #firefly_getStateRoot => #rpcResponseSuccess({ "stateRoot" : "0x" +String Keccak256( #rlpEncodeMerkleTree( #stateRoot( NETWORK ) ) ) }) ...
NETWORK
```
Transactions Root
-----------------
```k
syntax MerkleTree ::= #transactionsRoot( NetworkCell ) [function]
| #transactionsRootAux( MerkleTree, Int, List, MessagesCell ) [function]
// --------------------------------------------------------------------------------------------
rule #transactionsRoot( TXLIST MESSAGES ... )
=> #transactionsRootAux( .MerkleTree, 0, TXLIST, MESSAGES )
rule #transactionsRootAux( ( TREE => MerkleUpdate( TREE, #rlpEncodeWord(I), #rlpEncodeTransaction(TN, TP, TG, TT, TV, TD, TW, TR, TS) ) )
, ( I => I +Int 1 )
, ( ListItem( TXID ) => .List ) TXLIST
,
TXID
TN
TP
TG
TT
TV
TW
TR
TS
TD
...
)
rule #transactionsRootAux( TREE, _, .List, _ ) => TREE
syntax KItem ::= "#firefly_getTxRoot"
// -------------------------------------
rule #firefly_getTxRoot => #rpcResponseSuccess({ "transactionsRoot" : #getTxRoot( #getBlockByNumber(LATEST, BLOCKLIST, { NETWORK | BLOCK }) ) }) ...
BLOCKLIST
NETWORK
BLOCK
syntax String ::= #getTxRoot( BlockchainItem ) [function]
// ---------------------------------------------------------
rule #getTxRoot( { _ | TXROOT ... } ) => #unparseData( TXROOT, 32 )
```
Receipts Root
-------------
```k
syntax MerkleTree ::= #receiptsRoot( List, TxReceiptsCell ) [function]
| #receiptsRootAux( MerkleTree, Int, List, TxReceiptsCell ) [function]
// ------------------------------------------------------------------------------------------
rule #receiptsRoot( TXLIST, TXRECEIPTS )
=> #receiptsRootAux( .MerkleTree, 0, TXLIST, TXRECEIPTS )
rule #receiptsRootAux( ( TREE => MerkleUpdate( TREE, #rlpEncodeWord(I), #rlpEncodeReceipt(TS, TG, TB, TL) ) )
, ( I => I +Int 1 )
, ( ListItem(TXID) => .List ) _
,
TXID
TS
TG
TB
TL
...
...
)
rule #receiptsRootAux( TREE, _, .List, _ ) => TREE
syntax KItem ::= "#firefly_getReceiptsRoot"
// -------------------------------------------
rule #firefly_getReceiptsRoot => #rpcResponseSuccess({ "receiptsRoot" : #getReceiptRoot( #getBlockByNumber(LATEST, BLOCKLIST, { NETWORK | BLOCK }) ) }) ...
BLOCKLIST
NETWORK
BLOCK
syntax String ::= #getReceiptRoot( BlockchainItem ) [function]
// --------------------------------------------------------------
rule #getReceiptRoot( { _ | RCPTROOT ... } ) => #unparseData( RCPTROOT, 32 )
```
Timestamp Calls
---------------
```k
syntax KItem ::= "#firefly_getTime"
// -----------------------------------
rule #firefly_getTime => #rpcResponseSuccess(#unparseQuantity( TIME )) ...
TIME
syntax KItem ::= "#firefly_setTime"
// -----------------------------------
rule #firefly_setTime => #rpcResponseSuccess(true) ...
[ TIME:String, .JSONs ]
_ => #parseHexWord( TIME )
rule #firefly_setTime => #rpcResponseSuccess(false) ... [owise]
```
Gas Limit Call
--------------
```k
syntax KItem ::= "#firefly_setGasLimit"
// ---------------------------------------
rule #firefly_setGasLimit => #rpcResponseSuccess(true) ...
[ GLIMIT:String, .JSONs ]
_ => #parseWord( GLIMIT )
rule #firefly_setGasLimit => #rpcResponseSuccess(true) ...
[ GLIMIT:Int, .JSONs ]
_ => GLIMIT
rule #firefly_setGasLimit => #rpcResponseError(-32000, "firefly_setGasLimit requires exactly 1 argument") ... [owise]
```
Mining
------
```k
syntax KItem ::= "#evm_mine"
// ----------------------------
rule #evm_mine => #mineBlock ~> #rpcResponseSuccess("0x0") ... [owise]
rule #evm_mine => #mineBlock ~> #rpcResponseSuccess("0x0") ...
[ TIME:String, .JSONs ]
_ => #parseWord( TIME )
rule #evm_mine => #rpcResponseError(-32000, "Incorrect number of arguments. Method 'evm_mine' requires between 0 and 1 arguments.") ...
[ _ , _ , _:JSONs ]
syntax KItem ::= "#firefly_genesisBlock"
// ----------------------------------------
rule #firefly_genesisBlock => #updateTrieRoots ~> #pushBlockchainState ~> #rpcResponseSuccess(true) ...
_ => #padToWidth( 256, .ByteArray )
_ => 13478047122767188135818125966132228187941283477090363246179690878162135454535
syntax KItem ::= "#mineBlock"
// -----------------------------
rule #mineBlock
=> #finalizeBlock
~> #setParentHash #getBlockByNumber( LATEST, BLOCKLIST, { NETWORK | BLOCK } )
~> #updateTrieRoots
~> #saveState
~> #startBlock
~> #cleanTxLists
~> #clearGas
...
BLOCKLIST
NETWORK
BLOCK
syntax KItem ::= "#saveState"
| "#incrementBlockNumber"
| "#cleanTxLists"
| "#clearGas"
| "#setParentHash" BlockchainItem
| "#updateTrieRoots"
| "#updateStateRoot"
| "#updateTransactionsRoot"
| "#updateReceiptsRoot"
// --------------------------------------
rule #saveState => #incrementBlockNumber ~> #pushBlockchainState ...
rule #incrementBlockNumber => . ...
BN => BN +Int 1
rule #cleanTxLists => . ...
_ => .List
_ => .List
rule #clearGas => . ...
_ => 0
rule #setParentHash BCI => . ...
_ => #parseHexWord( Keccak256( #rlpEncodeBlock( BCI ) ) )
rule #updateTrieRoots => #updateStateRoot ~> #updateTransactionsRoot ~> #updateReceiptsRoot ...
rule #updateStateRoot => . ...
_ => #parseHexWord( Keccak256( #rlpEncodeMerkleTree( #stateRoot( NETWORK ) ) ) )
NETWORK
rule #updateTransactionsRoot => . ...
_ => #parseHexWord( Keccak256( #rlpEncodeMerkleTree( #transactionsRoot( NETWORK ) ) ) )
NETWORK
rule #updateReceiptsRoot => . ...
_ => #parseHexWord( Keccak256( #rlpEncodeMerkleTree( #receiptsRoot( TXLIST, TXRECEIPTS ) ) ) )
TXLIST
TXRECEIPTS
```
Blake2 Compression Function
---------------------------
```k
syntax KItem ::= "#firefly_blake2compress"
// ------------------------------------------
rule #firefly_blake2compress => #rpcResponseSuccess( Blake2Compress( Hex2Raw( DATA ) ) ) ...
[ DATA:String, .JSONs ]
```
Unimplemented Methods
---------------------
```k
syntax KItem ::= "#eth_coinbase"
| "#eth_getBlockByHash"
| "#eth_getBlockTransactionCountByHash"
| "#eth_getBlockTransactionCountByNumber"
| "#eth_getCompilers"
| "#eth_getFilterChanges"
| "#eth_getFilterLogs"
| "#eth_getLogs"
| "#eth_getTransactionByHash"
| "#eth_getTransactionByBlockHashAndIndex"
| "#eth_getTransactionByBlockNumberAndIndex"
| "#eth_hashrate"
| "#eth_newFilter"
| "#eth_protocolVersion"
| "#eth_signTypedData"
| "#eth_subscribe"
| "#eth_unsubscribe"
| "#net_peerCount"
| "#net_listening"
| "#eth_syncing"
| "#bzz_hive"
| "#bzz_info"
| "#debug_traceTransaction"
| "#miner_start"
| "#miner_stop"
| "#personal_sendTransaction"
| "#personal_unlockAccount"
| "#personal_newAccount"
| "#personal_lockAccount"
| "#personal_listAccounts"
| "#web3_sha3"
| "#shh_version"
// -------------------------------
rule #eth_coinbase => #rpcResponseUnimplemented ...
rule #eth_getBlockByHash => #rpcResponseUnimplemented ...
rule #eth_getBlockTransactionCountByHash => #rpcResponseUnimplemented ...
rule #eth_getBlockTransactionCountByNumber => #rpcResponseUnimplemented ...
rule #eth_getCompilers => #rpcResponseUnimplemented ...
rule #eth_getFilterChanges => #rpcResponseUnimplemented ...
rule #eth_getFilterLogs => #rpcResponseUnimplemented ...
rule #eth_getLogs => #rpcResponseUnimplemented ...
rule #eth_getTransactionByHash => #rpcResponseUnimplemented ...
rule #eth_getTransactionByBlockHashAndIndex => #rpcResponseUnimplemented ...
rule #eth_getTransactionByBlockNumberAndIndex => #rpcResponseUnimplemented ...
rule #eth_hashrate => #rpcResponseUnimplemented ...
rule #eth_newFilter => #rpcResponseUnimplemented ...
rule #eth_protocolVersion => #rpcResponseUnimplemented ...
rule #eth_signTypedData => #rpcResponseUnimplemented ...
rule #eth_subscribe => #rpcResponseUnimplemented ...
rule #eth_unsubscribe => #rpcResponseUnimplemented ...
rule #net_peerCount => #rpcResponseUnimplemented ...
rule #net_listening => #rpcResponseUnimplemented ...
rule #eth_syncing => #rpcResponseUnimplemented ...
rule #bzz_hive => #rpcResponseUnimplemented ...
rule #bzz_info => #rpcResponseUnimplemented ...
rule #debug_traceTransaction => #rpcResponseUnimplemented ...
rule #miner_start => #rpcResponseUnimplemented ...
rule #miner_stop => #rpcResponseUnimplemented ...
rule #personal_sendTransaction => #rpcResponseUnimplemented ...
rule #personal_unlockAccount => #rpcResponseUnimplemented ...
rule #personal_newAccount => #rpcResponseUnimplemented ...
rule #personal_lockAccount => #rpcResponseUnimplemented ...
rule #personal_listAccounts => #rpcResponseUnimplemented ...
rule #web3_sha3 => #rpcResponseUnimplemented ...
rule #shh_version => #rpcResponseUnimplemented ...
```
Opcode to String
----------------
```k
syntax String ::= #opcode2String ( OpCode ) [function]
// ------------------------------------------------------
rule #opcode2String (STOP ) => "STOP"
rule #opcode2String (ADD ) => "ADD"
rule #opcode2String (MUL ) => "MUL"
rule #opcode2String (SUB ) => "SUB"
rule #opcode2String (DIV ) => "DIV"
rule #opcode2String (SDIV ) => "SDIV"
rule #opcode2String (MOD ) => "MOD"
rule #opcode2String (SMOD ) => "SMOD"
rule #opcode2String (ADDMOD ) => "ADDMOD"
rule #opcode2String (MULMOD ) => "MULMOD"
rule #opcode2String (EXP ) => "EXP"
rule #opcode2String (SIGNEXTEND ) => "SIGNEXTEND"
rule #opcode2String (LT ) => "LT"
rule #opcode2String (GT ) => "GT"
rule #opcode2String (SLT ) => "SLT"
rule #opcode2String (SGT ) => "SGT"
rule #opcode2String (EQ ) => "EQ"
rule #opcode2String (ISZERO ) => "ISZERO"
rule #opcode2String (AND ) => "AND"
rule #opcode2String (EVMOR ) => "EVMOR"
rule #opcode2String (XOR ) => "XOR"
rule #opcode2String (NOT ) => "NOT"
rule #opcode2String (BYTE ) => "BYTE"
rule #opcode2String (SHL ) => "SHL"
rule #opcode2String (SHR ) => "SHR"
rule #opcode2String (SAR ) => "SAR"
rule #opcode2String (SHA3 ) => "SHA3"
rule #opcode2String (ADDRESS ) => "ADDRESS"
rule #opcode2String (BALANCE ) => "BALANCE"
rule #opcode2String (ORIGIN ) => "ORIGIN"
rule #opcode2String (CALLER ) => "CALLER"
rule #opcode2String (CALLVALUE ) => "CALLVALUE"
rule #opcode2String (CALLDATALOAD ) => "CALLDATALOAD"
rule #opcode2String (CALLDATASIZE ) => "CALLDATASIZE"
rule #opcode2String (CALLDATACOPY ) => "CALLDATACOPY"
rule #opcode2String (CODESIZE ) => "CODESIZE"
rule #opcode2String (CODECOPY ) => "CODECOPY"
rule #opcode2String (GASPRICE ) => "GASPRICE"
rule #opcode2String (EXTCODESIZE ) => "EXTCODESIZE"
rule #opcode2String (EXTCODECOPY ) => "EXTCODECOPY"
rule #opcode2String (RETURNDATASIZE) => "RETURNDATASIZE"
rule #opcode2String (RETURNDATACOPY) => "RETURNDATACOPY"
rule #opcode2String (EXTCODEHASH ) => "EXTCODEHASH"
rule #opcode2String (BLOCKHASH ) => "BLOCKHASH"
rule #opcode2String (COINBASE ) => "COINBASE"
rule #opcode2String (TIMESTAMP ) => "TIMESTAMP"
rule #opcode2String (NUMBER ) => "NUMBER"
rule #opcode2String (DIFFICULTY ) => "DIFFICULTY"
rule #opcode2String (GASLIMIT ) => "GASLIMIT"
rule #opcode2String (CHAINID ) => "CHAINID"
rule #opcode2String (SELFBALANCE ) => "SELFBALANCE"
rule #opcode2String (POP ) => "POP"
rule #opcode2String (MLOAD ) => "MLOAD"
rule #opcode2String (MSTORE ) => "MSTORE"
rule #opcode2String (MSTORE8 ) => "MSTORE8"
rule #opcode2String (SLOAD ) => "SLOAD"
rule #opcode2String (SSTORE ) => "SSTORE"
rule #opcode2String (JUMP ) => "JUMP"
rule #opcode2String (JUMPI ) => "JUMPI"
rule #opcode2String (PC ) => "PC"
rule #opcode2String (MSIZE ) => "MSIZE"
rule #opcode2String (GAS ) => "GAS"
rule #opcode2String (JUMPDEST ) => "JUMPDEST"
rule #opcode2String (PUSH(W) ) => "PUSH(" +String Int2String(W) +String ")"
rule #opcode2String (DUP(W) ) => "DUP(" +String Int2String(W) +String ")"
rule #opcode2String (SWAP(W) ) => "SWAP(" +String Int2String(W) +String ")"
rule #opcode2String (LOG(W) ) => "LOG(" +String Int2String(W) +String ")"
rule #opcode2String (CREATE ) => "CREATE"
rule #opcode2String (CALL ) => "CALL"
rule #opcode2String (CALLCODE ) => "CALLCODE"
rule #opcode2String (RETURN ) => "RETURN"
rule #opcode2String (DELEGATECALL ) => "DELEGATECALL"
rule #opcode2String (CREATE2 ) => "CREATE2"
rule #opcode2String (STATICCALL ) => "STATICCALL"
rule #opcode2String (REVERT ) => "REVERT"
rule #opcode2String (INVALID ) => "INVALID"
rule #opcode2String (SELFDESTRUCT ) => "SELFDESTRUCT"
rule #opcode2String (UNDEFINED(W) ) => "UNDEFINED(" +String Int2String(W) +String ")"
```
```k
endmodule
```