From 7c189953b45746b7471f67c9598d01c41236286a Mon Sep 17 00:00:00 2001 From: sc0Vu Date: Sat, 20 Jan 2024 16:59:36 +0800 Subject: [PATCH] Add unit test for TypedDataEncoder --- src/Contracts/TypedDataEncoder.php | 40 ++-- src/Contracts/Types/Bytes.php | 1 - test/unit/TypedDataEncoderTest.php | 306 +++++++++++++++++++++++++++++ 3 files changed, 329 insertions(+), 18 deletions(-) create mode 100644 test/unit/TypedDataEncoderTest.php diff --git a/src/Contracts/TypedDataEncoder.php b/src/Contracts/TypedDataEncoder.php index a5095e7a..a65d0f7c 100644 --- a/src/Contracts/TypedDataEncoder.php +++ b/src/Contracts/TypedDataEncoder.php @@ -108,7 +108,7 @@ protected function encodeField(array $types, string $name, string $type, mixed $ { if (array_key_exists($type, $types)) { if (is_null($value)) { - return ['bytes32', '0x' . str_repeat('0', 64)]; + return ['bytes32', '0x0000000000000000000000000000000000000000000000000000000000000000']; } else { return ['bytes32', Utils::sha3($this->encodeData($type, $types, $value))]; } @@ -126,14 +126,7 @@ protected function encodeField(array $types, string $name, string $type, mixed $ 'Invalid value for field ' . $name . ' of type ' . $type . ': expected array' ); } - $pos = strpos($type, '['); - if ($pos === false) { - // should not happen - throw new InvalidArgumentException( - 'Invalid value for field ' . $name . ' of type ' . $type . ': expected array' - ); - } - $parsedType = substr($type, 0, $pos); + $parsedType = $this->parseParentArrayType($type); $dataTypes = []; $dataValues = []; foreach ($value as $item) { @@ -145,7 +138,7 @@ protected function encodeField(array $types, string $name, string $type, mixed $ # the keccak hash of `encode((), ())` return ['bytes32', '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470']; } - return ["bytes32", Utils::sha3($this->ethabi->encode($dataTypes, $dataValues))]; + return ["bytes32", Utils::sha3($this->ethabi->encodeParameters($dataTypes, $dataValues))]; } else if ($type === 'bool') { return [$type, bool($value)]; } else if (substr($type, 0, 5) === 'bytes') { @@ -175,6 +168,21 @@ protected function strEndsWith(string $haystack, string $needle) return ($needle_len === 0 || 0 === substr_compare($haystack, $needle, - $needle_len)); } + /** + * parseParentArrayType + * + * @param string $type + * @return string + */ + protected function parseParentArrayType(string $type) + { + if ($this->strEndsWith($type, ']')) { + $pos = strrpos($type, '['); + $type = ($pos !== false) ? substr($type, 0, $pos) : $type; + } + return $type; + } + /** * parseArrayType * @@ -229,12 +237,10 @@ protected function encodeType(string $type, array $types) $result = ''; $unsortedDeps = $this->findType($type, $types); if (in_array($type, $unsortedDeps)) { - $unsortedDeps = array_splice($unsortedDeps, array_search($type, $unsortedDeps), 1); - } else { - sort($unsortedDeps); + array_splice($unsortedDeps, array_search($type, $unsortedDeps), 1); } - $deps = [ $type ]; - $deps = array_merge($unsortedDeps); + sort($unsortedDeps); + $deps = array_merge([ $type ], $unsortedDeps); foreach ($deps as $type) { $params = []; foreach ($types[$type] as $param) { @@ -307,12 +313,12 @@ protected function getPrimaryType(array $types) foreach ($types as $key => $typeFields) { foreach ($typeFields as $field) { $type = $this->parseArrayType($field['type']); - if (!in_array($type, $customTypes) && $type !== $key) { + if (in_array($type, $customTypes) && $type !== $key) { $customDepsTypes[] = $type; } } } - $primaryType = array_diff($customTypes, $customDepsTypes); + $primaryType = array_values(array_diff($customTypes, $customDepsTypes)); if (count($primaryType) === 0) { throw new InvalidArgumentException('Unable to determine primary type'); } diff --git a/src/Contracts/Types/Bytes.php b/src/Contracts/Types/Bytes.php index f85fa035..df5985a4 100644 --- a/src/Contracts/Types/Bytes.php +++ b/src/Contracts/Types/Bytes.php @@ -65,7 +65,6 @@ public function inputFormat($value, $name) if (mb_strlen($value) % 2 !== 0) { $value = "0" . $value; - // throw new InvalidArgumentException('The value to inputFormat has invalid length. Value: ' . $value); } if (mb_strlen($value) > 64) { diff --git a/test/unit/TypedDataEncoderTest.php b/test/unit/TypedDataEncoderTest.php new file mode 100644 index 00000000..d0ed5de3 --- /dev/null +++ b/test/unit/TypedDataEncoderTest.php @@ -0,0 +1,306 @@ + "Ether Mail", + "version" => "1", + "chainId" => 1, + "verifyingContract" => "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + ], + "0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f", + ], + [ + [ + "name" => "Ether Mail", + "version" => "1", + "chainId" => "1", + "verifyingContract" => "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + ], + "0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f", + ], + [ + [ + "name" => "Ether Mail", + "version" => 1, + "chainId" => 1, + "verifyingContract" => "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + ], + "0x902f609607aa38e1c768f260a84a1be97f3a9d65726d3e842fa5e36c6da393cb", + ], + [ + [ + "name" => "Ether Mail", + "version" => "1", + "chainId" => 1, + "verifyingContract" => "0xcccccccccccccccccccccccccccccccccccccccc", + ], + "0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f", + ], + [ + [ + "name" => "Ether Mail", + "version" => "1", + "chainId" => 1, + "verifyingContract" => "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + "salt" => "0xa9f4c8b7e576dc96308c361b46d32c04a00a0e5c2b0962d9f42be6891a95d139", # noqa => E501 + ], + "0x53d039704f24ce448de9dc98c5952dd85b7e7c22446a0b1cb47b43b901d00972", + ], + [ + [], + "0x6192106f129ce05c9075d319c1fa6ea9b3ae37cbd0c1ef92e2be7137bb07baa1", + ], + ]; + + /** + * hashDomainFailTests + * + * @var array + */ + protected $hashDomainFailTests = [ + + [ + [ + "name" => "Ether Mail", + "classification" => "1", + "chainId" => 1, + "verifyingContract" => "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + ], + InvalidArgumentException::class, + ], + [ + [ + "name" => "Ether Mail", + "version" => "1", + "chainId" => 1, + "verifyingContract" => "0xCcCCccccCCCC", + ], + InvalidArgumentException::class, + ], + ]; + + /** + * hashEIP712MessageTests + * + * @var array + */ + protected $hashEIP712MessageTests = [ + [ + [ + "from" => [ + "name" => "Cow", + ], + ], + [ + "Person" => [ + ["name" => "name", "type" => "string"], + ], + "Mail" => [ + ["name" => "from", "type" => "Person"], + ], + ], + "0xdfa5fd27fea278587b6c6a56d8e6cf2853b6698a4244afc1f5f526f04b2b70b3", + ], + [ + [ + "who" => [ + [ + "name" => "Cow", + ], + [ + "name" => "Dan", + ], + [ + "name" => "Eve", + ], + ], + ], + [ + "Person" => [ + ["name" => "name", "type" => "string"], + ], + "People" => [ + ["name" => "who", "type" => "Person[]"], + ], + ], + "0x978fbd13a22cb2ced753b88943583080d6e2fa20d9f5818181dd85ee26438745", + ], + [ + [ + "what" => [ + [ + [ + "name" => "Cow", + ], + ], + [ + [ + "name" => "Dan", + ], + ], + [ + [ + "name" => "Eve", + ], + ], + ], + ], + [ + "Stuff" => [ + ["name" => "name", "type" => "string"], + ], + "Things" => [ + ["name" => "what", "type" => "Stuff[][]"], + ], + ], + "0xb475420217c60fe1a7ad38c925c80f5d2c58e0fbb980684e4722f810ba9235d8", + ], + [ + [ + "what" => [ + [ + [ + "name" => "Cow", + ], + ], + [ + [ + "name" => "Dan", + ], + ], + [ + [ + "name" => "Eve", + ], + ], + ], + ], + [ + "Stuff" => [ + ["name" => "name", "type" => "string"], + ], + "Things" => [ + ["name" => "what", "type" => "Stuff[3][1]"], + ], + ], + "0xcdbacf00da86992e9443d46aa0206e27d670a6140155f8c505a68ba733c7e639", + ], + [ + [ + "what" => [ + [ + [ + "name" => "Cow", + ], + ], + [ + [ + "name" => "Dan", + ], + ], + [ + [ + "name" => "Eve", + ], + ], + ], + ], + [ + "Stuff" => [ + ["name" => "name", "type" => "string"], + ], + "Things" => [ + ["name" => "what", "type" => "Stuff[8][5]"], + ], + ], + "0xed3eb2f09fad610e8805f43a34704858e1ad7f3cd12b61e712b402be370b9001", + ], + [ + [ + "what" => "0x31323334353637383930616263646566", + ], + [ + "Things" => [ + ["name" => "what", "type" => "bytes16"], + ], + ], + "0x6825950a843718a846bf289599316a041180fd20d942ae0ca6106396ff797655", + ], + ]; + + /** + * setUp + * + * @return void + */ + public function setUp(): void + { + parent::setUp(); + + $this->typedDataEncoder = new TypedDataEncoder(); + } + + /** + * testHashDomainPass + * + * @return void + */ + public function testHashDomainPass() + { + $typedDataEncoder = $this->typedDataEncoder; + foreach ($this->hashDomainPassTests as $test) { + $result = $typedDataEncoder->hashDomain($test[0]); + $this->assertEquals($test[1], $result); + } + } + + /** + * testHashDomainFail + * + * @return void + */ + public function testHashDomainFail() + { + $typedDataEncoder = $this->typedDataEncoder; + foreach ($this->hashDomainFailTests as $test) { + $this->expectException($test[1]); + $result = $typedDataEncoder->hashDomain($test[0]); + } + } + + /** + * testHashEIP712Message + * + * @return void + */ + public function testHashEIP712Message() + { + $typedDataEncoder = $this->typedDataEncoder; + foreach ($this->hashEIP712MessageTests as $test) { + $result = $typedDataEncoder->hashEIP712Message($test[1], $test[0]); + $this->assertEquals($test[2], $result); + } + } +} \ No newline at end of file