ABI stands for application binary interface, it is the standard way to interact with contracts in the Ethereum ecosystem, both from outside the blockchain and for contract-to-contract interaction.
Data is encoded or decoded according to its type.
<?php use kornrunner\Keccak; include_once "../libraries/vendor/autoload.php"; class BtcschoolsABI { private $abi; public function __construct(array $abi) { $this->abi = $abi; } public function hexDec(string $hex): string { $dec = 0; $len = strlen($hex); for ($i = 1; $i <= $len; $i++) { $dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i)))); } return $dec; } public function decHex($dec): string { $last = bcmod($dec, 16); $remain = bcdiv(bcsub($dec, $last), 16); if ($remain == 0) { return dechex($last); } else { return self::DecHex($remain) . dechex($last); } } public function str2Hex(string $str): string { $hex = ""; for ($i = 0; $i < strlen($str); $i++) { $hex .= dechex(ord($str[$i])); } return $hex; } public function hex2Str(string $hex): string { $str = ""; for ($i = 0; $i < strlen($hex) - 1; $i += 2) { $str .= chr(hexdec($hex[$i] . $hex[$i + 1])); } return $str; } public function encodeSimpleType(string $type, $value) { $simpleType = preg_replace('/[^a-z]/', '', $type); if (in_array($simpleType, ["address", "uint", "int", "bool"])) { switch ($simpleType) { case "address": if (substr($value, 0, 2) === "0x") { $value = substr($value, 2); } break; case "uint": case "int": $value = $this->decHex($value); break; case "bool": $value = $value === true ? 1 : 0; break; } return substr(str_pad(strval($value), 64, "0", STR_PAD_LEFT), 0, 64); } else if (in_array($simpleType, ["bytes"])) { if (substr($value, 0, 2) === "0x") { $value = substr($value, 2); } switch($simpleType) { case "bytes"; return substr(str_pad(strval($value), 64, "0", STR_PAD_RIGHT), 0, 64); break; } } throw new Exception(sprintf('Cannot encode value of type "%s"', $type)); } public function encodeCall(string $name, ?array $args): string { $blocks = $this->abi; $encoded = ""; foreach($blocks as $block) { if ($block['type'] == 'function' and $block['name'] == $name) { $dataParts = []; $methodParamsTypes = []; if(count($block['inputs']) > 0) { foreach($block['inputs'] as $inputIdx=>$input) { $thisEncoded = ""; $methodParamsTypes[] = $input['type']; $arg = $args[$inputIdx]; if (preg_match('/^((uint|int){1}(8|16|32|64|128|256)?|bool|address|(bytes[0-9]{1,2}))$/', $input['type'])) { $encoded .= $this->encodeSimpleType($input['type'], $arg); //specially handle string array type } else if ($input['type'] == "string") { $thisEncoded = $this->str2Hex($arg); $thisEncoded = $this->encodeSimpleType("uint", strlen($thisEncoded)/2).$thisEncoded; $lengthUpTo = ceil(strlen($thisEncoded) / 64) * 64; $dataParts[] = substr(str_pad(strval($thisEncoded), $lengthUpTo, "0", STR_PAD_RIGHT), 0, $lengthUpTo); $idx = count($dataParts); $encoded .= "[" . str_pad($idx, 62, "0", STR_PAD_LEFT) . "]"; //specially handle byte array type } else if ($input['type'] == "bytes") { $thisEncoded = $arg; if (substr($thisEncoded, 0, 2) === "0x") { $thisEncoded = substr($thisEncoded, 2); } $thisEncoded = $this->encodeSimpleType("uint", strlen($thisEncoded)/2).$thisEncoded; $lengthUpTo = ceil(strlen($thisEncoded) / 64) * 64; $dataParts[] = substr(str_pad(strval($thisEncoded), $lengthUpTo, "0", STR_PAD_RIGHT), 0, $lengthUpTo); $idx = count($dataParts); $encoded .= "[" . str_pad($idx, 62, "0", STR_PAD_LEFT) . "]"; //specially handle other array type } else if (preg_match('/^((uint|int){1}(8|16|32|64|128|256)?|bool|address|bytes[0-9]{1,2})\[[0-9]*\]$/', $input['type'])) { $items = json_decode($arg,true); if (!in_array(substr($arg, 0, 1), array('[')) OR !in_array(substr($arg, -1), array("]"))) { throw new Exception( sprintf('Invalid array-json input for param "%s" with type "%s" (1)', $block['name'], $input['type']) ); } if (json_last_error() != JSON_ERROR_NONE) { throw new Exception( sprintf('Invalid array-json input for param "%s" with type "%s" (2)', $block['name'], $input['type']) ); } $thisEncoded .= $this->encodeSimpleType("uint", count($items)); foreach($items as $itemIdx=>$item) { $thisEncoded .= $this->encodeSimpleType($input['type'], $item); } $dataParts[] = $thisEncoded; $idx = count($dataParts); $encoded .= "[" . str_pad($idx, 62, "0", STR_PAD_LEFT) . "]"; } else { throw new Exception(sprintf('Cannot encode value of type "%s"', $type)); } } } foreach($dataParts as $k=>$dataPart) { $find = "[" . str_pad($k + 1, 62, "0", STR_PAD_LEFT) . "]"; $replaceWith = $this->encodeSimpleType("uint", strlen($encoded) / 2); $encoded = str_replace($find, $replaceWith, $encoded); $encoded .= $dataPart; } $encodedMethodCall = Keccak::hash($plain = sprintf('%s(%s)', $name, implode(",", $methodParamsTypes)), 256); return '0x' . substr($encodedMethodCall, 0, 8) . $encoded; } } return ""; } } if ($_SERVER['REQUEST_METHOD'] == 'POST') { $ajaxData = []; if ($_GET['action'] == 'parse_abi' OR ($_GET['action'] == 'submit' AND isset($_POST['abi']))) { $ajaxData['functions'] = []; $blocks = json_decode($_POST['abi'], true); if (@count($blocks) > 0) { foreach($blocks as $block) { if ($block['type'] == 'function') { $ajaxData['functions'][] = $block['name']; } } } else { $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'] = []; $blocks = json_decode($_POST['abi'], true); if (@count($blocks) > 0) { foreach($blocks as $block) { if ($block['type'] == 'function' and $block['name'] == $_POST['function']) { if ($block['inputs']) { foreach($block['inputs'] as $input) { $ajaxData['args'][] = $input['name'] . " ({$input['type']})"; } } } } } 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 (!$_POST['function']) { throw new Exception("Function should not be blank."); } $_POST['args'] = is_array($_POST['args']) ? $_POST['args'] : []; $decoded = json_decode($_POST['abi'], true); $abi = new BtcschoolsABI($decoded); $data = $abi->encodeCall($_POST['function'], $_POST['args']); ?> <div class="alert alert-success"> <h6 class="mt-3">Data (Hex)</h6> <textarea class="form-control" rows="5" id="comment" readonly><?php echo $data;?></textarea> </div> <?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="function">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 $function_name) { $optionSelected = $function_name == $_POST['function'] ? " selected='selected'" : ""; ?> <option value='<?php echo $function_name?>'<?php echo $optionSelected?>><?php echo $function_name?></option> <?php } } else { ?> <option value="">Please load from ABI ...</option> <?php } ?> </select> </div> <div class="form-group"> <label for="args">Function Arguments:</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 + ')'); if ('error' in j && j.error.length>0) { var error = true; } else { var error = false; } if (!error) { var functions = j.functions; if (functions.length > 0) { $('select#function',form).append('<option value=\'\' selected=\'selected\'>Please select ...</option>'); var x; for (x in functions) { $('select#function',form).append('<option value=\''+functions[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");
<?php use Web3\Contracts\Ethabi; use Web3\Contracts\Types\{Address, Boolean, Bytes, DynamicBytes, Integer, Str, Uinteger}; include_once "../libraries/vendor/autoload.php"; $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'] == 'function') { if (count($abiBlock['outputs']) > 0) { $ajaxData['functions'][] = $abiBlock['name']; } } } } else { $errmsg .= "Invalid ABI."; } if ($_GET['ajax'] == '1') { if ($errmsg) { $ajaxData['error'] = $errmsg; } die(json_encode($ajaxData)); } } } include_once("html_iframe_header.php"); $results = []; if ($_SERVER['REQUEST_METHOD'] == 'POST') { try { if (!$_POST['function'] OR !$_POST['abi']) { throw new Exception("Abi with selected function is needed."); } $abiBlocks = json_decode($_POST['abi'], true); if (@count($abiBlocks) > 0) { foreach($abiBlocks as $abiBlock) { if ($abiBlock['type'] == 'function' and $abiBlock['name'] == $_POST['function']) { $totalOutputs = count($abiBlock['outputs']); if ($totalOutputs) { $encoded = $_POST['response']; // Remove 0x prefix if (substr($encoded, 0, 2) === '0x') { $encoded = substr($encoded, 2); } if (strlen($encoded) != 64 * $totalOutputs) { throw new Exception("Response length is not valid."); } $decodeds = str_split($encoded, 64); foreach($decodeds as $k=> $decoded) { $result['name'] = $abiBlock['outputs'][$k]['name']; $result['type'] = $abiBlock['outputs'][$k]['type']; $result['hex'] = "0x". $decoded; $result['value'] = $ethAbi->decodeParameter($abiBlock['outputs'][$k]['type'], $decoded); $results[] = $result; } } } } } ?> <div class="alert alert-success"> <?php echo $totalOutputs?> outputs have been decoded. </div> <div class="table-responsive"> <table class="table table-bordered"> <tr><th>Name</th><th>Type</th><th>Hex</th><th>Decoded Value</th></tr> <?php foreach($results as $result) { ?> <tr> <td><?Php echo $result['name']?></td> <td><?Php echo $result['type']?></td> <td><?Php echo $result['hex']?></td> <td><?Php echo $result['value']?></td> </tr> <?Php } ?> </table> </div> <?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="response">Response To Decode:</label> <input class="form-control" type='text' name='response' id='response' value='<?php echo $_POST['response']?>'> <small>You may get this response from <a href="eth_call.php" target='_blank'>Ethereum Eth Call</a> to decode.</small> </div> <div class="form-group"> <label for="function">Function:</label> <select class="form-control" type='text' name='function' id='function' value='<?php echo $_POST['function']?>'> <?php if (@count($ajaxData['functions']) > 0) { ?> <option value=''>Please select ...</option> <?php foreach($ajaxData['functions'] as $function_name) { $optionSelected = $function_name == $_POST['function'] ? " selected='selected'" : ""; ?> <option value='<?php echo $function_name?>'<?php echo $optionSelected?>><?php echo $function_name?></option> <?php } } else { ?> <option value="">Please load from ABI ...</option> <?php } ?> </select> </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 + ')'); if ('error' in j && j.error.length>0) { var error = true; } else { var error = false; } if (!error) { var functions = j.functions; if (functions.length > 0) { $('select#function',form).append('<option value=\'\' selected=\'selected\'>Please select ...</option>'); var x; for (x in functions) { $('select#function',form).append('<option value=\''+functions[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> <?php include_once("html_iframe_footer.php");