Added abiDecoder class
This commit is contained in:
parent
bd40f1946a
commit
d2b9b4aa34
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,6 +2,7 @@ composer.phar
|
||||
/vendor/
|
||||
.phpintel/
|
||||
/.idea/
|
||||
test.php
|
||||
|
||||
|
||||
# Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file
|
||||
|
217
resources/abi.json
Normal file
217
resources/abi.json
Normal file
@ -0,0 +1,217 @@
|
||||
[
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "balance",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "remaining",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
}
|
||||
]
|
229
src/AbiDecoder.php
Normal file
229
src/AbiDecoder.php
Normal file
@ -0,0 +1,229 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user