Merge pull request #34 from sc0Vu/abi-decode

Abi decode
This commit is contained in:
Kuan-Cheng,Lai 2018-01-19 17:56:20 +08:00 committed by GitHub
commit bb87dcf866
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 397 additions and 11 deletions

View File

@ -3,6 +3,8 @@
[![Build Status](https://travis-ci.org/sc0Vu/web3.php.svg?branch=master)](https://travis-ci.org/sc0Vu/web3.php)
[![codecov](https://codecov.io/gh/sc0Vu/web3.php/branch/master/graph/badge.svg)](https://codecov.io/gh/sc0Vu/web3.php)
[![Join the chat at https://gitter.im/web3-php/web3.php](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg)](https://gitter.im/web3-php/web3.php)
[![Licensed under the MIT License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/sc0Vu/web3.php/blob/master/LICENSE)
A php interface for interacting with the Ethereum blockchain and ecosystem.
@ -173,7 +175,7 @@ $contract->at($contractAddress)->send($functionName, $params, $callback);
$contract->bytecode($bytecode)->estimateGas($params, $callback);
// estimate function gas
$contract->bytecode($bytecode)->estimateGas($functionName, $params, $callback);
$contract->at($contractAddress)->estimateGas($functionName, $params, $callback);
```
# Assign value to outside scope(from callback scope to outside scope)

View File

@ -153,7 +153,7 @@ class Ethabi
throw new InvalidArgumentException('encodeParameters number of types must equal to number of params.');
}
$typesLength = count($types);
$solidityTypes = array_fill(0, $typesLength, 0);
$solidityTypes = $this->getSolidityTypes($types);
foreach ($types as $key => $type) {
$match = [];
@ -189,6 +189,101 @@ class Ethabi
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++) {
$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) {
throw new InvalidArgumentException('Unsupport solidity parameter type: ' . $type);
}
$solidityTypes[$key] = $className;
}
}
}
return $solidityTypes;
}
/**
* encodeWithOffset
*

View File

@ -184,6 +184,16 @@ class SolidityType
return $count;
}
/**
* isDynamicType
*
* @return bool
*/
public function isDynamicType()
{
return false;
}
/**
* encode
*
@ -215,4 +225,54 @@ class SolidityType
}
return $this->inputFormat($value, $name);
}
/**
* decode
*
* @param mixed $value
* @param string $offset
* @param string $name
* @return array
*/
public function decode($value, $offset, $name)
{
if ($this->isDynamicArray($name)) {
$arrayOffset = (int) Utils::toBn('0x' . mb_substr($value, $offset * 2, 64))->toString();
$length = (int) Utils::toBn('0x' . mb_substr($value, $arrayOffset * 2, 64))->toString();
$arrayStart = $arrayOffset + 32;
$nestedName = $this->nestedName($name);
$nestedStaticPartLength = $this->staticPartLength($nestedName);
$roundedNestedStaticPartLength = floor(($nestedStaticPartLength + 31) / 32) * 32;
$result = [];
for ($i=0; $i<$length * $roundedNestedStaticPartLength; $i+=$roundedNestedStaticPartLength) {
$result[] = $this->decode($value, $arrayStart + $i, $nestedName);
}
return $result;
} elseif ($this->isStaticArray($name)) {
$length = $this->staticArrayLength($name);
$arrayStart = $offset;
$nestedName = $this->nestedName($name);
$nestedStaticPartLength = $this->staticPartLength($nestedName);
$roundedNestedStaticPartLength = floor(($nestedStaticPartLength + 31) / 32) * 32;
$result = [];
for ($i=0; $i<$length * $roundedNestedStaticPartLength; $i+=$roundedNestedStaticPartLength) {
$result[] = $this->decode($value, $arrayStart + $i, $nestedName);
}
return $result;
} elseif ($this->isDynamicType()) {
$dynamicOffset = (int) Utils::toBn('0x' . mb_substr($value, $offset * 2, 64))->toString();
$length = (int) Utils::toBn('0x' . mb_substr($value, $dynamicOffset * 2, 64))->toString();
$roundedLength = floor(($length + 31) / 32);
$param = mb_substr($value, $dynamicOffset * 2, ( 1 + $roundedLength) * 64);
return $this->outputFormat($param, $name);
}
$length = $this->staticPartLength($name);
$param = mb_substr($value, $offset * 2, $length * 2);
return $this->outputFormat($param, $name);
}
}

View File

@ -73,4 +73,16 @@ class Address extends SolidityType implements IType
return $value;
}
/**
* outputFormat
*
* @param mixed $value
* @param string $name
* @return string
*/
public function outputFormat($value, $name)
{
return '0x' . mb_substr($value, 24, 40);
}
}

View File

@ -64,4 +64,18 @@ class Boolean extends SolidityType implements IType
return '000000000000000000000000000000000000000000000000000000000000000' . $value;
}
/**
* outputFormat
*
* @param mixed $value
* @param string $name
* @return string
*/
public function outputFormat($value, $name)
{
$value = (int) mb_substr($value, 63, 1);
return (bool) $value;
}
}

View File

@ -75,4 +75,21 @@ class Bytes extends SolidityType implements IType
return $value . implode('', array_fill(0, $padding, '0'));
}
/**
* outputFormat
*
* @param mixed $value
* @param string $name
* @return string
*/
public function outputFormat($value, $name)
{
$checkZero = str_replace('0', '', $value);
if (empty($checkZero)) {
return '0';
}
return '0x' . $value;
}
}

View File

@ -74,4 +74,21 @@ class DynamicBytes extends SolidityType implements IType
return implode('', array_fill(0, 64-mb_strlen($bnHex), $padded)) . $bnHex . $value . implode('', array_fill(0, $padding, '0'));
}
/**
* outputFormat
*
* @param mixed $value
* @param string $name
* @return string
*/
public function outputFormat($value, $name)
{
$checkZero = str_replace('0', '', $value);
if (empty($checkZero)) {
return '0';
}
return '0x' . $value;
}
}

View File

@ -15,6 +15,7 @@ use Web3\Utils;
use Web3\Contracts\SolidityType;
use Web3\Contracts\Types\IType;
use Web3\Formatters\Integer as IntegerFormatter;
use Web3\Formatters\BigNumberFormatter;
class Integer extends SolidityType implements IType
{
@ -60,4 +61,22 @@ class Integer extends SolidityType implements IType
{
return IntegerFormatter::format($value);
}
/**
* outputFormat
*
* @param mixed $value
* @param string $name
* @return string
*/
public function outputFormat($value, $name)
{
$match = [];
if (preg_match('/^[0]+([a-f0-9]+)$/', $value, $match) === 1) {
// due to value without 0x prefix, we will parse as decimal
$value = '0x' . $match[1];
}
return BigNumberFormatter::format($value);
}
}

View File

@ -15,6 +15,7 @@ use Web3\Utils;
use Web3\Contracts\SolidityType;
use Web3\Contracts\Types\IType;
use Web3\Formatters\Integer as IntegerFormatter;
use Web3\Formatters\BigNumberFormatter;
class Str extends SolidityType implements IType
{
@ -65,4 +66,25 @@ class Str extends SolidityType implements IType
return $prefix . $value . implode('', array_fill(0, $padding, '0'));
}
/**
* outputFormat
*
* @param mixed $value
* @param string $name
* @return string
*/
public function outputFormat($value, $name)
{
$strLen = mb_substr($value, 0, 64);
$strValue = mb_substr($value, 64, 64);
$match = [];
if (preg_match('/^[0]+([a-f0-9]+)$/', $strLen, $match) === 1) {
$strLen = BigNumberFormatter::format('0x' . $match[1])->toString();
}
$strValue = mb_substr($strValue, 0, (int) $strLen * 2);
return Utils::hexToBin($strValue);
}
}

View File

@ -15,6 +15,7 @@ use Web3\Utils;
use Web3\Contracts\SolidityType;
use Web3\Contracts\Types\IType;
use Web3\Formatters\Integer as IntegerFormatter;
use Web3\Formatters\BigNumberFormatter;
class Uinteger extends SolidityType implements IType
{
@ -60,4 +61,22 @@ class Uinteger extends SolidityType implements IType
{
return IntegerFormatter::format($value);
}
/**
* outputFormat
*
* @param mixed $value
* @param string $name
* @return string
*/
public function outputFormat($value, $name)
{
$match = [];
if (preg_match('/^[0]+([a-f0-9]+)$/', $value, $match) === 1) {
// due to value without 0x prefix, we will parse as decimal
$value = '0x' . $match[1];
}
return BigNumberFormatter::format($value);
}
}

View File

@ -56,13 +56,14 @@ class EthabiTest extends TestCase
}';
/**
* tests
* from web3 eth.abi.encodeParameters test
* encodingTests
* from web3 abi.encodeParameter.js test
* and web3 eth.abi.encodeParameters test
* and web3 eth.abi.encodeParameter test
*
* @param array
*/
protected $tests = [
protected $encodingTests = [
[
'params' => [['uint256','string'], ['2345675643', 'Hello!%']],
'result' => '0x000000000000000000000000000000000000000000000000000000008bd02b7b0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000748656c6c6f212500000000000000000000000000000000000000000000000000'
@ -108,6 +109,54 @@ class EthabiTest extends TestCase
]
];
/**
* decodingTests
* from web3 abi.decodeParameter.js test
* and web3 eth.abi.decodeParameters test
* and web3 eth.abi.decodeParameter test
*
* @param array
*/
protected $decodingTests = [
[
'params' => [['uint256'], '0x0000000000000000000000000000000000000000000000000000000000000010'],
'result' => ['16']
], [
'params' => [['string'], '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000848656c6c6f212521000000000000000000000000000000000000000000000000'],
'result' => ['Hello!%!']
], [
'params' => [['uint256','string'], '0x000000000000000000000000000000000000000000000000000000008bd02b7b0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000748656c6c6f212500000000000000000000000000000000000000000000000000'],
'result' => ['2345675643', 'Hello!%']
], [
'params' => [['string'], '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'],
'result' => ['']
], [
'params' => [['int256'], '0x0000000000000000000000000000000000000000000000000000000000000000'],
'result' => ['0']
], [
'params' => [['uint256'], '0x0000000000000000000000000000000000000000000000000000000000000000'],
'result' => ['0']
], [
'params' => [['address'], '0x0000000000000000000000000000000000000000000000000000000000000000'],
'result' => ['0x0000000000000000000000000000000000000000']
], [
'params' => [['bool'], '0x0000000000000000000000000000000000000000000000000000000000000000'],
'result' => [false]
], [
'params' => [['bytes'], '0x0000000000000000000000000000000000000000000000000000000000000000'],
'result' => ['0']
], [
'params' => [['bytes32'], '0x0000000000000000000000000000000000000000000000000000000000000000'],
'result' => ['0']
], [
'params' => [['bytes32'], '0xdf32340000000000000000000000000000000000000000000000000000000000'],
'result' => ['0xdf32340000000000000000000000000000000000000000000000000000000000']
], [
'params' => [['bytes32[]'], '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002df32340000000000000000000000000000000000000000000000000000000000fdfd000000000000000000000000000000000000000000000000000000000000'],
'result' => ['0xdf32340000000000000000000000000000000000000000000000000000000000', '0xfdfd000000000000000000000000000000000000000000000000000000000000']
]
];
/**
* setUp
*
@ -182,6 +231,18 @@ class EthabiTest extends TestCase
$this->assertEquals($str, '0x095ea7b334ae44009aa867bfb386f5c3b4b443ac6f0ee573fa91c4608fbadfba');
}
/**
* testEncodeParameter
*
* @return void
*/
public function testEncodeParameter()
{
$abi = $this->abi;
$this->assertEquals('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', $abi->encodeParameter('int256', '-1'));
}
/**
* testEncodeParameters
*
@ -191,8 +252,45 @@ class EthabiTest extends TestCase
{
$abi = $this->abi;
foreach ($this->tests as $test) {
foreach ($this->encodingTests as $test) {
$this->assertEquals($test['result'], $abi->encodeParameters($test['params'][0], $test['params'][1]));
}
}
/**
* testDecodeParameter
*
* @return void
*/
public function testDecodeParameter()
{
$abi = $this->abi;
$this->assertEquals('16', $abi->decodeParameter('uint256', '0x0000000000000000000000000000000000000000000000000000000000000010')->toString());
$this->assertEquals('16', $abi->decodeParameter('uint256', '0x0000000000000000000000000000000000000000000000000000000000000010')->toString());
}
/**
* testDecodeParameters
*
* @return void
*/
public function testDecodeParameters()
{
$abi = $this->abi;
foreach ($this->decodingTests as $test) {
$decoded = $abi->decodeParameters($test['params'][0], $test['params'][1]);
foreach ($decoded as $key => $decoding) {
if (!is_array($decoding)) {
$this->assertEquals($test['result'][$key], $decoding);
} else {
foreach ($test['result'] as $rKey => $expected) {
$this->assertEquals($expected, $decoding[$rKey]);
}
}
}
}
}
}

View File

@ -123,9 +123,20 @@ class SolidityTypeTest extends TestCase
*
* @return void
*/
public function testEncode()
{
$type = $this->type;
$this->assertTrue(true);
}
// public function testEncode()
// {
// $type = $this->type;
// $this->assertTrue(true);
// }
/**
* testDecode
*
* @return void
*/
// public function testDecode()
// {
// $type = $this->type;
// $this->assertTrue(true);
// }
}