web3.php/src/AbiDecoder.php
2022-03-29 21:49:52 +03:00

229 lines
6.8 KiB
PHP

<?php
/**
* This file is part of web3.php package.
* @author Halil Beycan <halilbeycan0@gmail.com>
* @license MIT
*/
namespace Web3;
use Web3\Contracts\Ethabi;
use Web3\Contracts\Types\Address;
use Web3\Contracts\Types\Boolean;
use Web3\Contracts\Types\Bytes;
use Web3\Contracts\Types\DynamicBytes;
use Web3\Contracts\Types\Integer;
use Web3\Contracts\Types\Str;
use Web3\Contracts\Types\Uinteger;
use phpseclib\Math\BigInteger as BN;
class AbiDecoder
{
/**
* @var array
*/
private $abi = [];
/**
* @var Ethabi
*/
private $ethAbi;
/**
* @var array
*/
private $methodIds = [];
/**
* @param array|null $abi
*/
public function __construct(?array $abi = null)
{
$this->abi = is_null($abi) ? json_decode(file_get_contents(dirname(__DIR__) . '/resources/abi.json')) : $abi;
$this->ethAbi = new Ethabi([
'address' => new Address,
'bool' => new Boolean,
'bytes' => new Bytes,
'dynamicBytes' => new DynamicBytes,
'int' => new Integer,
'string' => new Str,
'uint' => new Uinteger,
]);
array_map([$this, 'parseMethodIds'], $this->abi);
}
/**
* @param object $input
* @return string
*/
private function typeToString(object $input) : string
{
if ($input->type === "tuple") {
return "(" . implode(',', array_map([$this, 'typeToString'], $input->components)) . ")";
}
return $input->type;
}
/**
* @param array $inputs
* @return string
*/
private function parseInputs(array $inputs) : string
{
return "(" . implode(',', array_map([$this, 'typeToString'], $inputs)) . ")";
}
/**
* @param object $obj
* @return void
*/
private function parseMethodIds(object $obj)
{
if (isset($obj->name)) {
$signature = Utils::sha3($obj->name . $this->parseInputs($obj->inputs));
if ($obj->type === "event") {
$this->methodIds[substr($signature, 2)] = $obj;
} else {
$this->methodIds[substr($signature, 2, 8)] = $obj;
}
}
}
/**
* @param string $input
* @return object
*/
public function decodeInput(string $input) : object
{
$abiItem = $this->methodIds[substr($input, 2, 8)];
$types = array_column($abiItem->inputs, 'type');
if ($abiItem) {
$decoded = $this->ethAbi->decodeParameters($types, substr($input, 10));
$retData = (object) [
"name" => $abiItem->name,
"params" => [],
];
for ($i = 0; $i < count($decoded); $i++) {
$param = $decoded[$i];
$parsedParam = $param;
$isArray = is_array($param);
$isUint = strpos($abiItem->inputs[$i]->type, "uint") !== false;
$isInt = strpos($abiItem->inputs[$i]->type, "int") !== false;
$isAddress = strpos($abiItem->inputs[$i]->type, "address") !== false;
if ($isUint || $isInt) {
if ($isArray) {
$parsedParam = array_map(function($val) {
return (new BN($val))->toString();
}, $param);
} else {
$parsedParam = (new BN($param))->toString();
}
}
if ($isAddress) {
if ($isArray) {
$parsedParam = array_map('strtolower', $param);
} else {
$parsedParam = strtolower($param);
}
}
$retData->params[] = [
"name" => $abiItem->inputs[$i]->name,
"value" => $parsedParam,
"type" => $abiItem->inputs[$i]->type,
];
}
}
return $retData;
}
/**
* @param array $logs
* @return array
*/
public function decodeLogs(array $logs) : array
{
$logs = array_filter($logs, function($log) {
return count($log->topics) > 0;
});
return array_map(function($log) {
$method = $this->methodIds[substr($log->topics[0], 2)];
if ($method) {
$logData = $log->data;
$decodedParams = [];
$dataIndex = 0;
$topicsIndex = 1;
$dataTypes = [];
array_map(function($input) use (&$dataTypes) {
if (!$input->indexed) {
$dataTypes[] = $input->type;
}
}, $method->inputs);
$decoded = $this->ethAbi->decodeParameters($dataTypes, substr($logData, 2));
array_map(function($input) use ($log, &$decodedParams, &$topicsIndex, &$dataIndex, $decoded) {
$decodedP = (object) [
"name" => $input->name,
"type" => $input->type,
];
if ($input->indexed) {
$decodedP->value = $log->topics[$topicsIndex];
$topicsIndex++;
} else {
$decodedP->value = $decoded[$dataIndex];
$dataIndex++;
}
if ($input->type === "address") {
$decodedP->value = strtolower($decodedP->value);
if (strlen($decodedP->value) > 42) {
$toRemove = strlen($decodedP->value) - 42;
$decodedP->value = "0x" . substr($decodedP->value, -40);
}
}
if (
$input->type === "uint256" ||
$input->type === "uint8" ||
$input->type === "int"
) {
if (is_string($decodedP->value) && str_starts_with($decodedP->value, '0x')) {
$decodedP->value = (new BN(substr($decodedP->value, 2), 16))->toString(10);
} else {
$decodedP->value = (new BN($decodedP->value))->toString(10);
}
}
$decodedParams[] = $decodedP;
}, $method->inputs);
return (object) [
"name" => $method->name,
"events" => $decodedParams,
"address" => $log->address,
];
}
}, $logs);
}
}