web3.php/src/Contracts/Ethabi.php
2021-01-02 18:53:20 +01:00

385 lines
12 KiB
PHP

<?php
/**
* This file is part of web3.php package.
*
* (c) Kuan-Cheng,Lai <alk03073135@gmail.com>
*
* @author Peter Lai <alk03073135@gmail.com>
* @license MIT
*/
namespace Web3\Contracts;
use InvalidArgumentException;
use stdClass;
use Web3\Utils;
use Web3\Formatters\IntegerFormatter;
class Ethabi
{
/**
* types
*
* @var array
*/
protected $types = [];
/**
* construct
*
* @param array $types
* @return void
*/
public function __construct($types=[])
{
if (!is_array($types)) {
$types = [];
}
$this->types = $types;
}
/**
* get
*
* @param string $name
* @return mixed
*/
public function __get($name)
{
$method = 'get' . ucfirst($name);
if (method_exists($this, $method)) {
return call_user_func_array([$this, $method], []);
}
return false;
}
/**
* set
*
* @param string $name
* @param mixed $value
* @return mixed
*/
public function __set($name, $value)
{
$method = 'set' . ucfirst($name);
if (method_exists($this, $method)) {
return call_user_func_array([$this, $method], [$value]);
}
return false;
}
/**
* callStatic
*
* @param string $name
* @param array $arguments
* @return void
*/
public static function __callStatic($name, $arguments)
{
//
}
/**
* encodeFunctionSignature
*
* @param string|stdClass|array $functionName
* @return string
*/
public function encodeFunctionSignature($functionName)
{
if (!is_string($functionName)) {
$functionName = Utils::jsonMethodToString($functionName);
}
return mb_substr(Utils::sha3($functionName), 0, 10);
}
/**
* encodeEventSignature
* TODO: Fix same event name with different params
*
* @param string|stdClass|array $eventName
* @return string
*/
public function encodeEventSignature($eventName)
{
if (!is_string($eventName)) {
$eventName = Utils::jsonMethodToString($eventName);
}
return Utils::sha3($eventName);
}
/**
* encodeParameter
*
* @param string $type
* @param mixed $param
* @return string
*/
public function encodeParameter($type, $param)
{
if (!is_string($type)) {
throw new InvalidArgumentException('The type to encodeParameter must be string.');
}
return $this->encodeParameters([$type], [$param]);
}
/**
* encodeParameters
*
* @param stdClass|array $types
* @param array $params
* @return string
*/
public function encodeParameters($types, $params)
{
// change json to array
if ($types instanceof stdClass && isset($types->inputs)) {
$types = Utils::jsonToArray($types, 2);
}
if (is_array($types) && isset($types['inputs'])) {
$inputTypes = $types;
$types = [];
foreach ($inputTypes['inputs'] as $input) {
if (isset($input['type'])) {
$types[] = $input['type'];
}
}
}
if (count($types) !== count($params)) {
throw new InvalidArgumentException('encodeParameters number of types must equal to number of params.');
}
$typesLength = count($types);
$solidityTypes = $this->getSolidityTypes($types);
$encodes = array_fill(0, $typesLength, '');
foreach ($solidityTypes as $key => $type) {
$encodes[$key] = call_user_func([$type, 'encode'], $params[$key], $types[$key]);
}
$dynamicOffset = 0;
foreach ($solidityTypes as $key => $type) {
$staticPartLength = $type->staticPartLength($types[$key]);
$roundedStaticPartLength = floor(($staticPartLength + 31) / 32) * 32;
if ($type->isDynamicType($types[$key]) || $type->isDynamicArray($types[$key])) {
$dynamicOffset += 32;
} else {
$dynamicOffset += $roundedStaticPartLength;
}
}
return '0x' . $this->encodeMultiWithOffset($types, $solidityTypes, $encodes, $dynamicOffset);
}
/**
* decodeParameter
*
* @param string $type
* @param mixed $param
* @return string
*/
public function decodeParameter($type, $param)
{
if (!is_string($type)) {
throw new InvalidArgumentException('The type to decodeParameter must be string.');
}
return $this->decodeParameters([$type], $param)[0];
}
/**
* decodeParameters
*
* @param stdClass|array $type
* @param string $param
* @return string
*/
public function decodeParameters($types, $param)
{
if (!is_string($param)) {
throw new InvalidArgumentException('The type or param to decodeParameters must be string.');
}
// change json to array
if ($types instanceof stdClass && isset($types->outputs)) {
$types = Utils::jsonToArray($types, 2);
}
if (is_array($types) && isset($types['outputs'])) {
$outputTypes = $types;
$types = [];
foreach ($outputTypes['outputs'] as $output) {
if (isset($output['type'])) {
$types[] = $output['type'];
}
}
}
$typesLength = count($types);
$solidityTypes = $this->getSolidityTypes($types);
$offsets = array_fill(0, $typesLength, 0);
for ($i=0; $i<$typesLength; $i++) {
$offsets[$i] = $solidityTypes[$i]->staticPartLength($types[$i]);
}
for ($i=1; $i<$typesLength; $i++) {
$offsets[$i] += $offsets[$i - 1];
}
for ($i=0; $i<$typesLength; $i++) {
$offsets[$i] -= $solidityTypes[$i]->staticPartLength($types[$i]);
}
$result = [];
$param = mb_strtolower(Utils::stripZero($param));
for ($i=0; $i<$typesLength; $i++) {
if (isset($outputTypes['outputs'][$i]['name']) && empty($outputTypes['outputs'][$i]['name']) === false) {
$result[$outputTypes['outputs'][$i]['name']] = $solidityTypes[$i]->decode($param, $offsets[$i], $types[$i]);
} else {
$result[$i] = $solidityTypes[$i]->decode($param, $offsets[$i], $types[$i]);
}
}
return $result;
}
/**
* getSolidityTypes
*
* @param array $types
* @return array
*/
protected function getSolidityTypes($types)
{
if (!is_array($types)) {
throw new InvalidArgumentException('Types must be array');
}
$solidityTypes = array_fill(0, count($types), 0);
foreach ($types as $key => $type) {
$match = [];
if (preg_match('/^([a-zA-Z]+)/', $type, $match) === 1) {
if (isset($this->types[$match[0]])) {
$className = $this->types[$match[0]];
if (call_user_func([$this->types[$match[0]], 'isType'], $type) === false) {
// check dynamic bytes
if ($match[0] === 'bytes') {
$className = $this->types['dynamicBytes'];
} else {
throw new InvalidArgumentException('Unsupport solidity parameter type: ' . $type);
}
}
$solidityTypes[$key] = $className;
}
}
}
return $solidityTypes;
}
/**
* encodeWithOffset
*
* @param string $type
* @param \Web3\Contracts\SolidityType $solidityType
* @param mixed $encode
* @param int $offset
* @return string
*/
protected function encodeWithOffset($type, $solidityType, $encoded, $offset)
{
if ($solidityType->isDynamicArray($type)) {
$nestedName = $solidityType->nestedName($type);
$nestedStaticPartLength = $solidityType->staticPartLength($type);
$result = $encoded[0];
if ($solidityType->isDynamicArray($nestedName)) {
$previousLength = 2;
for ($i=0; $i<count($encoded); $i++) {
if (isset($encoded[$i - 1])) {
$previousLength += abs($encoded[$i - 1][0]);
}
$result .= IntegerFormatter::format($offset + $i * $nestedStaticPartLength + $previousLength * 32);
}
}
for ($i=0; $i<count($encoded); $i++) {
// $bn = Utils::toBn($result);
// $divided = $bn->divide(Utils::toBn(2));
// if (is_array($divided)) {
// $additionalOffset = (int) $divided[0]->toString();
// } else {
// $additionalOffset = 0;
// }
$additionalOffset = floor(mb_strlen($result) / 2);
$result .= $this->encodeWithOffset($nestedName, $solidityType, $encoded[$i], $offset + $additionalOffset);
}
return mb_substr($result, 64);
} elseif ($solidityType->isStaticArray($type)) {
$nestedName = $solidityType->nestedName($type);
$nestedStaticPartLength = $solidityType->staticPartLength($type);
$result = '';
if ($solidityType->isDynamicArray($nestedName)) {
$previousLength = 0;
for ($i=0; $i<count($encoded); $i++) {
if (isset($encoded[$i - 1])) {
$previousLength += abs($encoded[$i - 1])[0];
}
$result .= IntegerFormatter::format($offset + $i * $nestedStaticPartLength + $previousLength * 32);
}
}
for ($i=0; $i<count($encoded); $i++) {
// $bn = Utils::toBn($result);
// $divided = $bn->divide(Utils::toBn(2));
// if (is_array($divided)) {
// $additionalOffset = (int) $divided[0]->toString();
// } else {
// $additionalOffset = 0;
// }
$additionalOffset = floor(mb_strlen($result) / 2);
$result .= $this->encodeWithOffset($nestedName, $solidityType, $encoded[$i], $offset + $additionalOffset);
}
return $result;
}
return $encoded;
}
/**
* encodeMultiWithOffset
*
* @param array $types
* @param array $solidityTypes
* @param array $encodes
* @param int $dynamicOffset
* @return string
*/
protected function encodeMultiWithOffset($types, $solidityTypes, $encodes, $dynamicOffset)
{
$result = '';
foreach ($solidityTypes as $key => $type) {
if ($type->isDynamicType($types[$key]) || $type->isDynamicArray($types[$key])) {
$result .= IntegerFormatter::format($dynamicOffset);
$e = $this->encodeWithOffset($types[$key], $type, $encodes[$key], $dynamicOffset);
$dynamicOffset += floor(mb_strlen($e) / 2);
} else {
$result .= $this->encodeWithOffset($types[$key], $type, $encodes[$key], $dynamicOffset);
}
}
foreach ($solidityTypes as $key => $type) {
if ($type->isDynamicType($types[$key]) || $type->isDynamicArray($types[$key])) {
$e = $this->encodeWithOffset($types[$key], $type, $encodes[$key], $dynamicOffset);
// $dynamicOffset += floor(mb_strlen($e) / 2);
$result .= $e;
}
}
return $result;
}
}