Skip to content

Commit

Permalink
Merge pull request #343 from web3p/add-eip-712
Browse files Browse the repository at this point in the history
Add EIP712 TypedDataEncoder
sc0Vu authored Jan 21, 2024
2 parents 779a095 + 545a1fc commit 8b4c5bf
Showing 11 changed files with 5,147 additions and 33 deletions.
33 changes: 13 additions & 20 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">

<testsuite name="Web3.php unit test">
<directory suffix="Test.php">./test/unit</directory>
</testsuite>

<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
</phpunit>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" backupStaticAttributes="false" bootstrap="vendor/autoload.php" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
<testsuite name="Web3.php unit test">
<directory suffix="Test.php">./test/unit</directory>
</testsuite>
<php>
<ini name="memory_limit" value="256M"/>
</php>
</phpunit>
400 changes: 400 additions & 0 deletions src/Contracts/TypedDataEncoder.php

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion src/Contracts/Types/Bytes.php
Original file line number Diff line number Diff line change
@@ -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) {
6 changes: 2 additions & 4 deletions src/Formatters/HexFormatter.php
Original file line number Diff line number Diff line change
@@ -25,10 +25,8 @@ class HexFormatter implements IFormatter
*/
public static function format($value)
{
$value = Utils::toString($value);
$value = mb_strtolower($value);

if (Utils::isZeroPrefixed($value)) {
if (is_string($value) && Utils::isZeroPrefixed($value)) {
$value = mb_strtolower($value);
return $value;
} else {
$value = Utils::toHex($value, true);
13 changes: 12 additions & 1 deletion src/Formatters/IntegerFormatter.php
Original file line number Diff line number Diff line change
@@ -34,11 +34,22 @@ public static function format($value)
}
$bn = Utils::toBn($value);
$bnHex = $bn->toHex(true);
$bnHexLen = mb_strlen($bnHex);
$padded = mb_substr($bnHex, 0, 1);

if ($bnHexLen >= $digit) {
$zeroPos = mb_strrpos($bnHex, '0');
if ($zeroPos !== false) {
$bnHex = mb_substr($bnHex, $zeroPos, $digit);
$bnHexLen = mb_strlen($bnHex);
}
if ($bnHexLen >= $digit) {
return mb_substr($bnHex, 0, $digit);
}
}
if ($padded !== 'f') {
$padded = '0';
}
}
return implode('', array_fill(0, $digit-mb_strlen($bnHex), $padded)) . $bnHex;
}
}
6 changes: 5 additions & 1 deletion src/Utils.php
Original file line number Diff line number Diff line change
@@ -87,7 +87,7 @@ class Utils
*/
public static function toHex($value, $isPrefix=false)
{
if (is_numeric($value)) {
if (is_int($value) || is_float($value)) {
// turn to hex number
$bn = self::toBn($value);
$hex = $bn->toHex(true);
@@ -121,6 +121,10 @@ public static function hexToBin($value)
if (self::isZeroPrefixed($value)) {
$count = 1;
$value = str_replace('0x', '', $value, $count);
// avoid suffix 0
if (strlen($value) % 2 > 0) {
$value = '0' . $value;
}
}
return pack('H*', $value);
}
20 changes: 20 additions & 0 deletions test/TestCase.php
Original file line number Diff line number Diff line change
@@ -58,6 +58,14 @@ class TestCase extends BaseTestCase
*/
protected $EMPTY_ADDRESS = '0x0000000000000000000000000000000000000000';

/**
* test fixtures
*
* TODO: add more fixtures
* @var array
*/
protected $testFixtures = [];

/**
* setUp
*/
@@ -84,6 +92,18 @@ public function setUp(): void
$this->coinbase = $coinbase;
// }
});

// load test fixtures
$fixtureFileName = __DIR__ . '/fixtures/typed-data.json';
$json = \file_get_contents($fixtureFileName);
if (false === $json) {
throw new \RuntimeException("Unable to load file {$fixtureFileName}");
}

$data = \json_decode($json, true);
$this->testFixtures = [
'typed-data' => $data
];
}

/**
4,369 changes: 4,369 additions & 0 deletions test/fixtures/typed-data.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion test/unit/HexFormatterTest.php
Original file line number Diff line number Diff line change
@@ -41,7 +41,7 @@ public function testFormat()
$this->assertEquals($hex, '0xabce');

$hex = $formatter->format('123');
$this->assertEquals($hex, '0x7b');
$this->assertEquals($hex, '0x313233');

$hex = $formatter->format(12);
$this->assertEquals($hex, '0xc');
320 changes: 320 additions & 0 deletions test/unit/TypedDataEncoderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,320 @@
<?php

namespace Test\Unit;

use InvalidArgumentException;
use Test\TestCase;
use Web3\Utils;
use Web3\Contracts\TypedDataEncoder;

class TypedDataEncoderTest extends TestCase
{
/**
* typedDataEncoder
*
* @var \Web3\Contracts\TypedDataEncoder
*/
protected $typedDataEncoder;

/**
* hashDomainPassTests
*
* @var array
*/
protected $hashDomainPassTests = [
[
[
"name" => "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);
}
}

/**
* testEncodeTypedDataFixtures
*
* @return void
*/
public function testEncodeTypedDataFixtures()
{
$typedDataEncoder = $this->typedDataEncoder;
foreach ($this->testFixtures['typed-data'] as $test) {
$result = $typedDataEncoder->encodeTypedData($test['domain'], $test['types'], $test['data']);
$this->assertEquals($test['digest'], Utils::sha3($result));
}
}
}
10 changes: 5 additions & 5 deletions test/unit/UtilsTest.php
Original file line number Diff line number Diff line change
@@ -153,24 +153,24 @@ public function testToHex()
$this->assertEquals('0x' . $this->testHex, Utils::toHex('hello world', true));

$this->assertEquals('0x927c0', Utils::toHex(0x0927c0, true));
$this->assertEquals('0x927c0', Utils::toHex('600000', true));
$this->assertEquals('0x363030303030', Utils::toHex('600000', true));
$this->assertEquals('0x927c0', Utils::toHex(600000, true));
$this->assertEquals('0x927c0', Utils::toHex(new BigNumber(600000), true));

$this->assertEquals('0xea60', Utils::toHex(0x0ea60, true));
$this->assertEquals('0xea60', Utils::toHex('60000', true));
$this->assertEquals('0x3630303030', Utils::toHex('60000', true));
$this->assertEquals('0xea60', Utils::toHex(60000, true));
$this->assertEquals('0xea60', Utils::toHex(new BigNumber(60000), true));

$this->assertEquals('0x', Utils::toHex(0x00, true));
$this->assertEquals('0x', Utils::toHex('0', true));
$this->assertEquals('0x30', Utils::toHex('0', true));
$this->assertEquals('0x', Utils::toHex(0, true));
$this->assertEquals('0x', Utils::toHex(new BigNumber(0), true));

$this->assertEquals('0x30', Utils::toHex(48, true));
$this->assertEquals('0x30', Utils::toHex('48', true));
$this->assertEquals('0x3438', Utils::toHex('48', true));
$this->assertEquals('30', Utils::toHex(48));
$this->assertEquals('30', Utils::toHex('48'));
$this->assertEquals('3438', Utils::toHex('48'));

$this->assertEquals('0x30', Utils::toHex(new BigNumber(48), true));
$this->assertEquals('0x30', Utils::toHex(new BigNumber('48'), true));

0 comments on commit 8b4c5bf

Please sign in to comment.