From a6725ec3a3a3c99021edf175d7b6009171bd9f0a Mon Sep 17 00:00:00 2001 From: sc0Vu Date: Thu, 25 Jan 2024 21:58:43 +0800 Subject: [PATCH] Update Array / Tuple decoder --- src/Contracts/Ethabi.php | 63 ++++--------------------- src/Contracts/SolidityType.php | 69 ++++------------------------ src/Contracts/Types/Address.php | 4 +- src/Contracts/Types/Boolean.php | 4 +- src/Contracts/Types/Bytes.php | 6 +-- src/Contracts/Types/DynamicArray.php | 28 +++++------ src/Contracts/Types/DynamicBytes.php | 4 +- src/Contracts/Types/Integer.php | 14 ++---- src/Contracts/Types/SizedArray.php | 43 ++++++++++------- src/Contracts/Types/Str.php | 4 +- src/Contracts/Types/Tuple.php | 31 +++++++------ src/Contracts/Types/Uinteger.php | 14 ++---- src/Utils.php | 14 ++++++ 13 files changed, 108 insertions(+), 190 deletions(-) diff --git a/src/Contracts/Ethabi.php b/src/Contracts/Ethabi.php index 01ed4e0c..f24d5e71 100644 --- a/src/Contracts/Ethabi.php +++ b/src/Contracts/Ethabi.php @@ -307,41 +307,6 @@ protected function parseAbiTypes($types) 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; - } - /** * encodeFunctionSignature * @@ -449,6 +414,7 @@ public function decodeParameters($types, $param) } // change json to array + $outputTypes = []; if ($types instanceof stdClass && isset($types->outputs)) { $types = Utils::jsonToArray($types, 2); } @@ -463,29 +429,18 @@ public function decodeParameters($types, $param) } } $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)); + $abiTypes = $this->parseAbiTypes($types); - for ($i=0; $i<$typesLength; $i++) { + // decode with tuple type + $results = []; + $decodeResults = $this->types['tuple']->decode(Utils::stripZero($param), 0, $abiTypes); + 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]); + $results[$outputTypes['outputs'][$i]['name']] = $decodeResults[$i]; } else { - $result[$i] = $solidityTypes[$i]->decode($param, $offsets[$i], $types[$i]); + $results[$i] = $decodeResults[$i]; } } - - return $result; + return $results; } } \ No newline at end of file diff --git a/src/Contracts/SolidityType.php b/src/Contracts/SolidityType.php index 53c005cd..a30e170d 100644 --- a/src/Contracts/SolidityType.php +++ b/src/Contracts/SolidityType.php @@ -198,76 +198,23 @@ public function isDynamicType() */ public function encode($value, $name) { - // if ($this->isDynamicArray($name)) { - // $length = count($value); - // $nestedName = $this->nestedName($name); - // $result = []; - // $result[] = IntegerFormatter::format($length); - - // foreach ($value as $val) { - // $result[] = $this->encode($val, $nestedName); - // } - // return $result; - // } elseif ($this->isStaticArray($name)) { - // $length = $this->staticArrayLength($name); - // $nestedName = $this->nestedName($name); - // $result = []; - - // foreach ($value as $val) { - // $result[] = $this->encode($val, $nestedName); - // } - // return $result; - // } return $this->inputFormat($value, $name); } /** * decode * - * @param mixed $value - * @param string $offset - * @param string $name + * @param string $value + * @param integer $offset + * @param array $abiTypes * @return array */ - public function decode($value, $offset, $name) + public function decode($value, $offset, $abiTypes) { - 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); + if (!is_string($value)) { + throw new InvalidArgumentException('Decode value should be string'); } - $length = $this->staticPartLength($name); - $param = mb_substr($value, $offset * 2, $length * 2); - - return $this->outputFormat($param, $name); + $value = mb_substr($value, $offset); + return $this->outputFormat($value, $abiTypes); } } \ No newline at end of file diff --git a/src/Contracts/Types/Address.php b/src/Contracts/Types/Address.php index 3f0d9acd..a73b589a 100644 --- a/src/Contracts/Types/Address.php +++ b/src/Contracts/Types/Address.php @@ -78,10 +78,10 @@ public function inputFormat($value, $name) * outputFormat * * @param mixed $value - * @param string $name + * @param array $abiType * @return string */ - public function outputFormat($value, $name) + public function outputFormat($value, $abiType) { return '0x' . mb_substr($value, 24, 40); } diff --git a/src/Contracts/Types/Boolean.php b/src/Contracts/Types/Boolean.php index b63671a5..f5b6e77d 100644 --- a/src/Contracts/Types/Boolean.php +++ b/src/Contracts/Types/Boolean.php @@ -69,10 +69,10 @@ public function inputFormat($value, $name) * outputFormat * * @param mixed $value - * @param string $name + * @param array $abiType * @return string */ - public function outputFormat($value, $name) + public function outputFormat($value, $abiType) { $value = (int) mb_substr($value, 63, 1); diff --git a/src/Contracts/Types/Bytes.php b/src/Contracts/Types/Bytes.php index 84af2d29..913d7a54 100644 --- a/src/Contracts/Types/Bytes.php +++ b/src/Contracts/Types/Bytes.php @@ -80,17 +80,17 @@ public function inputFormat($value, $name) * outputFormat * * @param mixed $value - * @param string $name + * @param array $abiType * @return string */ - public function outputFormat($value, $name) + public function outputFormat($value, $abiType) { $checkZero = str_replace('0', '', $value); if (empty($checkZero)) { return '0'; } - if (preg_match('/^bytes([0-9]*)/', $name, $match) === 1) { + if (preg_match('/^bytes([0-9]*)/', $abiType['type'], $match) === 1) { $size = intval($match[1]); $length = 2 * $size; $value = mb_substr($value, 0, $length); diff --git a/src/Contracts/Types/DynamicArray.php b/src/Contracts/Types/DynamicArray.php index 9b89f155..a3b49aff 100644 --- a/src/Contracts/Types/DynamicArray.php +++ b/src/Contracts/Types/DynamicArray.php @@ -66,22 +66,24 @@ public function inputFormat($value, $name) /** * outputFormat * - * @param mixed $value - * @param string $name - * @return string + * @param string $value + * @param array $abiType + * @return array */ - public function outputFormat($value, $name) + public function outputFormat($value, $abiType) { - $checkZero = str_replace('0', '', $value); - - if (empty($checkZero)) { - return '0'; + if (!is_array($abiType)) { + throw new InvalidArgumentException('Invalid abiType to decode sized array, expected: array'); } - if (preg_match('/^bytes([0-9]*)/', $name, $match) === 1) { - $size = intval($match[1]); - $length = 2 * $size; - $value = mb_substr($value, 0, $length); + $lengthHex = mb_substr($value, 0, 64); + $length = (int) Utils::hexToNumber($lengthHex); + $offset = 64; + $results = []; + $decoder = $abiType['coders']; + for ($i = 0; $i < $length; $i++) { + $results[] = $decoder['solidityType']->decode($value, $offset, $decoder); + $offset += 64; } - return '0x' . $value; + return $results; } } \ No newline at end of file diff --git a/src/Contracts/Types/DynamicBytes.php b/src/Contracts/Types/DynamicBytes.php index 7ca3d738..0adbce6f 100644 --- a/src/Contracts/Types/DynamicBytes.php +++ b/src/Contracts/Types/DynamicBytes.php @@ -84,10 +84,10 @@ public function inputFormat($value, $name) * outputFormat * * @param mixed $value - * @param string $name + * @param array $abiType * @return string */ - public function outputFormat($value, $name) + public function outputFormat($value, $abiType) { $checkZero = str_replace('0', '', $value); diff --git a/src/Contracts/Types/Integer.php b/src/Contracts/Types/Integer.php index da12a510..a1495944 100644 --- a/src/Contracts/Types/Integer.php +++ b/src/Contracts/Types/Integer.php @@ -66,17 +66,11 @@ public function inputFormat($value, $name) * outputFormat * * @param mixed $value - * @param string $name - * @return string + * @param array $abiType + * @return BigNumber */ - public function outputFormat($value, $name) + public function outputFormat($value, $abiType) { - $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); + return BigNumberFormatter::format('0x' . mb_substr($value, 0, 64)); } } \ No newline at end of file diff --git a/src/Contracts/Types/SizedArray.php b/src/Contracts/Types/SizedArray.php index 9bed51a5..866ab2e6 100644 --- a/src/Contracts/Types/SizedArray.php +++ b/src/Contracts/Types/SizedArray.php @@ -53,40 +53,49 @@ public function isDynamicType() * inputFormat * * @param mixed $value - * @param string $name + * @param array $abiType * @return string */ - public function inputFormat($value, $name) + public function inputFormat($value, $abiType) { if (!is_array($value)) { throw new InvalidArgumentException('Encode value must be array'); } - $length = is_array($name) ? $this->staticArrayLength($name['type']) : 0; + $length = is_array($abiType) ? $this->staticArrayLength($abiType['type']) : 0; if ($length === 0 || count($value) > $length) { throw new InvalidArgumentException('Invalid value to encode sized array, expected: ' . $length . ', but got ' . count($value)); } - return parent::inputFormat($value, $name); + return parent::inputFormat($value, $abiType); } /** * outputFormat * - * @param mixed $value - * @param string $name - * @return string + * @param string $value + * @param array $abiType + * @return array */ - public function outputFormat($value, $name) + public function outputFormat($value, $abiType) { - $checkZero = str_replace('0', '', $value); - - if (empty($checkZero)) { - return '0'; + if (!is_array($abiType)) { + throw new InvalidArgumentException('Invalid abiType to decode sized array, expected: array'); + } + $length = is_array($abiType) ? $this->staticArrayLength($abiType['type']) : 0; + $offset = 0; + if ($abiType['dynamic']) { + $valueLengthHex = mb_substr($value, 0, 64); + $valueLength = (int) Utils::hexToNumber($valueLengthHex) / 32; + if ($length !== $valueLength) { + throw new InvalidArgumentException('Invalid sized array length decode, expected: ' . $lenght . ', but got ' . $valueLength); + } + $offset += 64; } - if (preg_match('/^bytes([0-9]*)/', $name, $match) === 1) { - $size = intval($match[1]); - $length = 2 * $size; - $value = mb_substr($value, 0, $length); + var_dump('Length: ' . $length . ' Decode length: ' . $valueLength, $abiType['coders']); + $results = []; + $decoder = $abiType['coders']; + for ($i = 0; $i < $length; $i++) { + $results[] = $decoder['solidityType']->decode($value, $offset, $decoder); } - return '0x' . $value; + return $results; } } \ No newline at end of file diff --git a/src/Contracts/Types/Str.php b/src/Contracts/Types/Str.php index c8d8fc41..cb8ec013 100644 --- a/src/Contracts/Types/Str.php +++ b/src/Contracts/Types/Str.php @@ -71,10 +71,10 @@ public function inputFormat($value, $name) * outputFormat * * @param mixed $value - * @param string $name + * @param array $abiType * @return string */ - public function outputFormat($value, $name) + public function outputFormat($value, $abiType) { $strLen = mb_substr($value, 0, 64); $strValue = mb_substr($value, 64); diff --git a/src/Contracts/Types/Tuple.php b/src/Contracts/Types/Tuple.php index 6b58c3c3..a1311640 100644 --- a/src/Contracts/Types/Tuple.php +++ b/src/Contracts/Types/Tuple.php @@ -54,7 +54,7 @@ public function isDynamicType() * inputFormat * * @param mixed $value - * @param string $name + * @param string $abiTypes * @return string */ public function inputFormat($params, $abiTypes) @@ -102,22 +102,25 @@ public function inputFormat($params, $abiTypes) /** * outputFormat * - * @param mixed $value - * @param string $name + * @param string $value + * @param array $abiTypes * @return string */ - public function outputFormat($value, $name) + public function outputFormat($value, $abiTypes) { - $checkZero = str_replace('0', '', $value); - - if (empty($checkZero)) { - return '0'; - } - if (preg_match('/^bytes([0-9]*)/', $name, $match) === 1) { - $size = intval($match[1]); - $length = 2 * $size; - $value = mb_substr($value, 0, $length); + $results = []; + $staticOffset = 0; + foreach ($abiTypes as $key => $abiType) { + if ($abiType['dynamic']) { + $startPosHex = mb_substr($value, $staticOffset, 64); + $startPos = Utils::hexToNumber($startPosHex); + $staticOffset += 64; + $results[] = $abiType['solidityType']->decode($value, $startPos * 2, $abiType); + } else { + $results[] = $abiType['solidityType']->decode($value, $staticOffset, $abiType); + $staticOffset += 64; + } } - return '0x' . $value; + return $results; } } \ No newline at end of file diff --git a/src/Contracts/Types/Uinteger.php b/src/Contracts/Types/Uinteger.php index 566413d4..9b528c42 100644 --- a/src/Contracts/Types/Uinteger.php +++ b/src/Contracts/Types/Uinteger.php @@ -66,17 +66,11 @@ public function inputFormat($value, $name) * outputFormat * * @param mixed $value - * @param string $name - * @return string + * @param array $abiType + * @return BigNumber */ - public function outputFormat($value, $name) + public function outputFormat($value, $abiType) { - $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); + return BigNumberFormatter::format('0x' . mb_substr($value, 0, 64)); } } \ No newline at end of file diff --git a/src/Utils.php b/src/Utils.php index 6546f9e6..8e576c47 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -549,4 +549,18 @@ public static function toBn($number) } return $bn; } + + /** + * hexToNumber + * + * @param string $hexNumber + * @return int + */ + public static function hexToNumber($hexNumber) + { + if (!self::isZeroPrefixed($hexNumber)) { + $hexNumber = '0x' . $hexNumber; + } + return intval(self::toBn($hexNumber)->toString()); + } } \ No newline at end of file