diff --git a/src/Contract.php b/src/Contract.php index 87ad1fc..530406c 100644 --- a/src/Contract.php +++ b/src/Contract.php @@ -32,6 +32,7 @@ use Web3\Validators\StringValidator; use Web3\Validators\TagValidator; use Web3\Validators\QuantityValidator; use Web3\Formatters\AddressFormatter; +use Web3\Contracts\Types\Tuple; class Contract { @@ -162,6 +163,7 @@ class Contract 'int' => new Integer, 'string' => new Str, 'uint' => new Uinteger, + 'tuple' => new Tuple, ]); } diff --git a/src/Contracts/Ethabi.php b/src/Contracts/Ethabi.php index 694a563..aced751 100644 --- a/src/Contracts/Ethabi.php +++ b/src/Contracts/Ethabi.php @@ -238,7 +238,11 @@ class Ethabi 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]); + if($types[$i] == "tuple"){ + $result[$i] = $solidityTypes[$i]->decodeTuple($outputTypes['outputs'][$i],$param); + }else{ + $result[$i] = $solidityTypes[$i]->decode($param, $offsets[$i], $types[$i]); + } } } diff --git a/src/Contracts/SolidityType.php b/src/Contracts/SolidityType.php index 5adf572..e5ebd45 100644 --- a/src/Contracts/SolidityType.php +++ b/src/Contracts/SolidityType.php @@ -13,9 +13,21 @@ namespace Web3\Contracts; use Web3\Utils; use Web3\Formatters\IntegerFormatter; +use InvalidArgumentException; +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 Web3\Contracts\Types\Tuple; + class SolidityType { + private array $types = []; + /** * construct * @@ -23,6 +35,27 @@ class SolidityType */ // public function __construct() {} + /** + * set types + */ + private function setTypes() + { + if(!empty($this->types)){ + return $this->types; + } + $this->types = [ + 'address' => new Address, + 'bool' => new Boolean, + 'bytes' => new Bytes, + 'dynamicBytes' => new DynamicBytes, + 'int' => new Integer, + 'string' => new Str, + 'uint' => new Uinteger, + 'tuple' => new Tuple, + ]; + return $this->types; + } + /** * get * @@ -270,4 +303,81 @@ class SolidityType return $this->outputFormat($param, $name); } + + /** + * decode struct data + * + * @author abaowu + * @param array $type tuple struct + * @param mix $value tuple value + * @return array + */ + public function decodeTuple($type, $value) + { + $outputTypes = $type["components"]; + $typesLength = count($outputTypes); + $solidityTypes = $this->getSolidityTypes($outputTypes); + $offsets = array_fill(0, $typesLength, 0); + + for ($i=0; $i<$typesLength; $i++) { + $offsets[$i] = $solidityTypes[$i]->staticPartLength($outputTypes[$i]['type']); + } + for ($i=1; $i<$typesLength; $i++) { + $offsets[$i] += $offsets[$i - 1]; + } + for ($i=0; $i<$typesLength; $i++) { + $offsets[$i] -= $solidityTypes[$i]->staticPartLength($outputTypes[$i]['type']); + } + $result = []; + $param = mb_strtolower(Utils::stripZero($value)); + + for ($i=0; $i<$typesLength; $i++) { + if (isset($outputTypes[$i]['name']) && empty($outputTypes[$i]['name']) === false) { + $result[$outputTypes[$i]['name']] = $solidityTypes[$i]->decode($param, $offsets[$i], $outputTypes[$i]['type']); + } else { + $result[$i] = $solidityTypes[$i]->decode($param, $offsets[$i], $outputTypes[$i]['type']); + } + } + + 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); + $this->setTypes(); + + foreach ($types as $key => $type) { + $match = []; + $type = $type['type']; + 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; + } + } \ No newline at end of file diff --git a/src/Contracts/Types/Tuple.php b/src/Contracts/Types/Tuple.php new file mode 100644 index 0000000..de2e77f --- /dev/null +++ b/src/Contracts/Types/Tuple.php @@ -0,0 +1,86 @@ + + * + * @author abaowu + * @license MIT + */ + +namespace Web3\Contracts\Types; + +use Web3\Contracts\SolidityType; +use Web3\Contracts\Types\IType; + +class Tuple extends SolidityType implements IType +{ + /** + * construct + * + * @return void + */ + public function __construct() + { + // + } + + /** + * isType + * + * @param string $name + * @return bool + */ + public function isType($name) + { + return (preg_match('/^tuple(\[([0-9]*)\])*$/', $name) === 1); + } + + /** + * isDynamicType + * + * @return bool + */ + public function isDynamicType() + { + return true; + } + + /** + * inputFormat + * to do: iban + * + * @param mixed $value + * @param string $name + * @return string + */ + public function inputFormat($value, $name) + { + // $value = (string) $value; + + // if (Utils::isAddress($value)) { + // $value = mb_strtolower($value); + + // if (Utils::isZeroPrefixed($value)) { + // $value = Utils::stripZero($value); + // } + // } + // $value = IntegerFormatter::format($value); + + return $value; + } + + /** + * outputFormat + * + * @param mixed $value + * @param string $name + * @return string + */ + public function outputFormat($value, $name) + { + // return '0x' . mb_substr($value, 24, 40); + return $value; + } +} \ No newline at end of file