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 RD 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 )) ... 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 RD 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 ```