The log mechanism is one of the most important features of a virtual machine, which is used to output specific binary data and record it in transaction-receipt while the virtual machine is running a contract. Based on the Log mechanism, the Event feature is implemented to help developers confirm, check, and quickly retrieve specific states of a smart contract. This article introduces the basics of the Event mechanism and how to decode the Event Log.
// 375 + 375 * 0 + 160 (address always 160 bits) = 535 event NewEvent(address addr) // 375 + 375 * 1 + 0 = 750 event NewEvent(address indexed addr)
Maximum records return from eth_getLogs is up to 10,000 otherwise error is return.
<?php include_once "../libraries/vendor/autoload.php"; use Web3\Contracts\Ethabi; use Web3\Contracts\Types\{Address, Boolean, Bytes, DynamicBytes, Integer, Str, Uinteger}; $hosts = ["https://mainnet.infura.io"=>"https://mainnet.infura.io","https://ropsten.infura.io"=>"https://ropsten.infura.io", "https://cloudflare-eth.com"=>"https://cloudflare-eth.com"]; function bcdechex($dec) { $last = bcmod($dec, 16); $remain = bcdiv(bcsub($dec, $last), 16); if($remain == 0) { return dechex($last); } else { return bcdechex($remain).dechex($last); } } $ethAbi = new Ethabi(['address' => new Address,'bool' => new Boolean,'bytes' => new Bytes,'dynamicBytes' => new DynamicBytes,'int' => new Integer,'string' => new Str,'uint' => new Uinteger,]); if ($_SERVER['REQUEST_METHOD'] == 'POST') { $ajaxData = []; if ($_GET['action'] == 'parse_abi' OR ($_GET['action'] == 'submit' AND isset($_POST['abi']))) { $ajaxData['functions'] = []; $abiBlocks = json_decode($_POST['abi'], true); if (@count($abiBlocks) > 0) { foreach($abiBlocks as $abiBlock) { if ($abiBlock['type'] == 'event') { $types = []; if ($abiBlock['inputs']) { foreach($abiBlock['inputs'] as $input) { $types[] = $input['type']; } } $eventSignature = $ethAbi->encodeEventSignature($plain = "{$abiBlock['name']}(".implode(",",$types).")"); $ajaxData['functions'][$abiBlock['name']] = $abiBlock['name'] . " (".$eventSignature.")"; } } } else { if ($_GET['ajax'] == '1') { $errmsg .= "Invalid ABI."; } } if ($_GET['ajax'] == '1') { if ($errmsg) { $ajaxData['error'] = $errmsg; } die(json_encode($ajaxData)); } } if ($_GET['action'] == 'get_args' OR ($_GET['action'] == 'submit' AND isset($_POST['abi']) AND isset($_POST['function'])) ) { $ajaxData['args'] = []; $abiBlocks = json_decode($_POST['abi'], true); $topicCounter = 1; if (@count($abiBlocks) > 0) { foreach($abiBlocks as $abiBlock) { if ($abiBlock['type'] == 'event' and $abiBlock['name'] == $_POST['function']) { if ($abiBlock['inputs']) { foreach($abiBlock['inputs'] as $input) { if ($input['indexed'] === true) { $ajaxData['args'][] = "<span class='grey_info'>topic {$topicCounter }</span> ".$input['name'] . " ({$input['type']})"; $topicCounter++; } } } } } } if ($_GET['ajax'] == '1') { if ($errmsg) { $ajaxData['error'] = $errmsg; } die(json_encode($ajaxData)); } } } include_once("html_iframe_header.php"); if ($_SERVER['REQUEST_METHOD'] == 'POST') { try { if (!in_array($_POST['host'], array_keys($hosts))) { throw new Exception("Please provide valid host."); } $_POST['args'] = is_array($_POST['args']) ? $_POST['args'] : []; $abiBlocks = json_decode($_POST['abi'], true); $eventSignature = ""; $inputTypes = []; $topics = []; if (@count($abiBlocks) > 0) { foreach($abiBlocks as $abiBlock) { if ($abiBlock['type'] == 'event' and $abiBlock['name'] == $_POST['function']) { if ($abiBlock['inputs']) { foreach($abiBlock['inputs'] as $input) { $inputTypes[] = $input['type']; } } $eventSignature = $ethAbi->encodeEventSignature($plain = "{$abiBlock['name']}(".implode(",",$inputTypes).")"); $topics[0] = $eventSignature; foreach($_POST['args'] as $k=>$arg) { if (strlen($_POST['args'][$k]) > 0) { $topics[$k+1] = $ethAbi->encodeParameter($inputTypes[$k], $_POST['args'][$k]); } else { //null, tell filter to ignore this position of topic $topics[$k+1] = null; } } } } } $url = $_POST['host'] . "/" . $_POST['path']; $ch = curl_init(); $requestId = time(); $params = []; $params['jsonrpc']= "2.0"; $params['method'] = 'eth_getLogs'; $params['params'] = [["address"=>$_POST['contract_address'], "fromBlock"=>"0x".bcdechex($_POST["block_num"]), "toBlock"=>"0x".bcdechex($_POST["block_num"]), "topics"=>$topics]]; $params['id'] = $requestId; curl_setopt($ch, CURLOPT_URL,$url); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS,$req = json_encode($params)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]); $resp = curl_exec($ch); if ($resp === false) { throw new Exception("curl_exec return false."); } if (strlen($err = @curl_error($ch)) > 0) { $errno = @curl_errno($ch); throw new Exception( "{$err} ({$errno})." ); } $result = json_decode($resp,true); if ($result['id'] != $requestId) { throw new Exception("Invalid request id."); } $result = $result['result']; curl_close ($ch); ?> <div class="alert alert-success"> <h6 class="mt-3">Host</h6> <textarea class="form-control" rows="1" readonly><?php echo $url;?></textarea> <h6 class="mt-3">JSON-RPC Request</h6> <textarea class="form-control" rows="5" id="comment" readonly><?php echo $req;?></textarea> <h6 class="mt-3">JSON-RPC Response</h6> <textarea class="form-control" rows="1" id="comment" readonly><?php echo $resp;?></textarea> </div> <?Php if ($result) { ?> <h6 class="mt-3"><?php echo (int)count($result) ?> Log(s) Found</h6> <pre><?php print_r($result);}?></pre> <?php } catch (Exception $e) { $errmsg .= "Problem found. " . $e->getMessage(); } } if ($errmsg) { ?> <div class="alert alert-danger"> <strong>Error!</strong> <?php echo $errmsg?> </div> <?php } ?> <form id='this_form' action='?action=submit' method='post'> <div class="form-group"> <label for="host">Host To Receive RPC:</label> <div class="input-group mb-3"> <select id="host" name="host" class="form-control" > <?php foreach($hosts as $k=>$v) { echo "<option value='{$k}'".($k == $_POST['host'] ? " selected": "").">{$v}</option>"; } ?> </select> <div class="input-group-append"> <span class="input-group-text"> / </span> </div> <input class="form-control" type='text' name='path' id='path' value='<?php echo $_POST['path']?>' placeholder="Put extra path or blank if it does not."> </div> </div> <div class="form-group"> <label for="block_num">Block Number:</label> <input class="form-control" type='text' name='block_num' id='block_num' value='<?php echo $_POST['block_num']?>'> </div> <div class="form-group"> <label for="contract_address">Contract Address:</label> <input class="form-control" type='text' name='contract_address' id='contract_address' value='<?php echo $_POST['contract_address']?>'> </div> <div class="form-group"> <label for="function"><span class='grey_info'>topic 0</span> Event Function:</label> <select class="form-control" type='text' name='function' id='function' value='<?php echo $_POST['function']?>' onchange=" var self = $(this); var form = self.closest('form'); $('p#args_panel',form).empty(); $('p#args_panel',form).html(''); $.ajax({ url: '?ajax=1&action=get_args', type: 'post', data: $('#this_form :input'), success:function(result){ try { j = eval('(' + result + ')'); if ('error' in j && j.error.length>0) { var error = true; } else { var error = false; } if (!error) { var args = j.args; if (args.length > 0) { var x; for (x in args) { $('p#args_panel',form).append('<div class=\'form-group\'>'+ args[x] +':<input class=\'form-control\' type=\'text\' name=\'args[]\'/></div>'); } } else { $('p#args_panel',form).html(' No arguments'); } } else { alert(j.error); } } catch(e) { alert('Invalid Json Format.'); } }, complete:function() { } }); "> <?php if (@count($ajaxData['functions']) > 0) { ?> <option value=''>Please select ...</option> <?php foreach($ajaxData['functions'] as $k => $v) { $optionSelected = ($k == $_POST['function'] ? " selected='selected'" : ""); ?> <option value='<?php echo $k?>'<?php echo $optionSelected?>><?php echo $v?></option> <?php } } else { ?> <option value="">Please load from ABI ...</option> <?php } ?> </select> </div> <div class="form-group"> <label for="args">Indexed Inputs:</label> <p id='args_panel'> </p> </div> <div class="form-group"> <label for="gas_price">ABI:</label> <div class="input-group mb-3"> <textarea class="form-control" rows="10" name='abi' id='abi'><?php echo $_POST['abi']?></textarea> <div class="input-group-append"> <input class="btn btn-success" type="button" value="Load" onclick=" var self = $(this); self.val('...'); var form = self.closest('form'); $('select#function',form).empty(); $.ajax({ url: '?ajax=1&action=parse_abi', type: 'post', data: $('#this_form :input'), success:function(result){ try { j = eval('(' + result + ')'); console.log(j); if ('error' in j && j.error.length>0) { var error = true; } else { var error = false; } if (!error) { var functions = j.functions; if (functions !== undefined) { $('select#function',form).append('<option value=\'\' selected=\'selected\'>Please select ...</option>'); var x; for (x in functions) { $('select#function',form).append('<option value=\''+x+'\'>'+functions[x]+'</option>'); } } } else { $('select#function',form).prepend('<option value=\'\' selected=\'selected\'>Please load from ABI ...</option>'); alert(j.error); } } catch(e) { $('select#function',form).prepend('<option value=\'\' selected=\'selected\'>Please load from ABI ...</option>'); alert('Invalid Json Format.'); } }, complete:function() { self.val('Load'); } }); "/> </div> </div> </div> <input type='submit' class="btn btn-success btn-block"/> </form> <script> <?php if ($_GET['action'] == 'submit') { if (@count($ajaxData['args']) > 0) { foreach($ajaxData['args'] as $k=>$arg) { ?> $('p#args_panel').append("<div class='form-group'><?php echo $arg?>:<input class='form-control' type='text' name='args[]' value='<?php echo $_POST['args'][$k]?>'/></div>"); <?php } } else { ?> $('p#args_panel').html(" No arguments"); <?php } } ?> </script> <?php include_once("html_iframe_footer.php");
In Event Log Decoder section, we have demonstrate usage of both "NULL" and "AND". This section we will show more examples include usage of "OR" for better understanding.
Topics Array | Description |
---|---|
[] | Anything |
[ A ] | topic[0] = A |
[ A, null ] | |
[ null, B ] | topic[1] = B |
[ null, [ B ] ] | |
[ null, [ B ], null ] | |
[ A, B ] | (topic[0] = A) AND (topic[1] = B) |
[ A, [ B ] ] | |
[ A, [ B ], null ] | |
[ [ A, B ] ] | (topic[0] = A) OR (topic[0] = B) |
[ [ A, B ], null ] | |
[ [ A, B ], [ C, D ] ] | [ (topic[0] = A) OR (topic[0] = B) ] AND [ (topic[1] = C) OR (topic[1] = D) ] |
Methods listed in table below are useful to get data with polling methods such as Websocket, Long Polling, Short Polling, HTTP2, Server-Sent Events(sse) and etc.
Method | Description |
---|---|
eth_newFilter | Take same parameter as eth_getLogs, but return a filter ID. |
eth_newBlockFilter | Return filter ID, and filter effects on new block. |
eth_getFilterLogs | Pass in filter ID that generated by eth_newFilter and as the result this return an array of logs. |
eth_getFilterChanges | Polling method. Its Return subject to type of filter ID, either an array of logs or new block which occurred since last poll. Filter ID can be implicitly remove if time out occur due to inactivity. |
eth_uninstallFilter | Explicitly remove pre-existing filter ID. |
This section is alias of Bloom Filter page.