Ethereum PHP
PHP interface to Ethereum JSON-RPC API.
EthQ.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Ethereum\DataType;
4 
5 /* @see https://pear.php.net/package/Math_BigInteger/docs/latest/Math_BigInteger/Math_BigInteger.html Math_BigInteger documentation */
7 
8 
14 class EthQ extends EthD
15 {
16  // Validation properties.
17  private $intTypes = ['int', 'uint'];
18 
19  // @var $value Math_BigInteger Math big integer pear library.
20  public $value;
21 
27  protected $abi;
28 
29  // Non not RLP encoded values have a hex padding length of 64 (strlen).
30  // See:https://github.com/ethereum/wiki/wiki/RLP
31  const HEXPADDING = 64;
32 
43  public function __construct($val, array $params = [])
44  {
45  parent::__construct($val, $params);
46  }
47 
61  public function validate($val, array $params)
62  {
63  $big_int = null;
64  $abi = isset($params['abi']) ? $params['abi'] : null;
65 
66  if ($this->hasHexPrefix($val)) {
67 
68  // All Hex strings are lowercase.
69  $val = strtolower($val);
70 
71  // Some positive values are unprefixed. Enforcing left pad.
72  if (strlen(self::removeHexPrefix($val)) < self::HEXPADDING) {
73  $val = $this->padLeft($val);
74  }
75 
76  // A negative base will encode using two's compliment.
77  if ($val[2] === 'f') {
78  $big_int = new Math_BigInteger($val, -16);
79  $big_int->is_negative = true;
80  }
81  else {
82  // defaults to unsigned int if no abi is given.
83  $big_int = new Math_BigInteger($val, 16);
84  }
85  }
86  elseif (is_numeric($val)) {
87  if ($val < 0) {
88  $big_int = new Math_BigInteger($val);
89  $big_int->is_negative = true;
90  }
91  else {
92  $big_int = new Math_BigInteger($val);
93  }
94  }
95  if ($big_int && is_a($big_int, 'Math_BigInteger')) {
96 
97  $this->abi = $this->getAbiFromNumber($big_int);
98  if ($abi) {
99  // Will throw if ABI param is formally incorrect.
100  $this->validateAbi($abi);
101 
102  if ($this->abi !== $abi) {
103 
104  // Check if calculated ABI is a subset of the given API Param.
105  if ($this->getLength($this->abi) < $this->getLength($abi)) {
106  $this->abi = $abi;
107  }
108  else {
109  throw new \InvalidArgumentException(
110  'Given ABI (' . $abi . ') does not match number given number: ' . $val);
111  }
112  }
113  }
114  return $big_int;
115  }
116  else {
117  throw new \InvalidArgumentException('Can not decode Hex number: ' . $val);
118  }
119  }
120 
121 
131  private static function padLeft(string $val)
132  {
133  $unprefixed = self::removeHexPrefix($val);
134  $fillUp = self::HEXPADDING - (strlen($unprefixed) % self::HEXPADDING);
135  return self::ensureHexPrefix(str_repeat("0", $fillUp) . $unprefixed);
136  }
137 
146  protected function getAbiFromNumber(Math_BigInteger $number)
147  {
148  $abi_l = null;
149  $negative = $number->is_negative;
150 
151  if ($negative) {
152  $number = $number->multiply(new Math_BigInteger(-1));
153  }
154 
155  foreach ($this->getValidLengths() as $exp) {
156 
157  $max_for_exp = new Math_BigInteger(2);
158  $max_for_exp = $max_for_exp->bitwise_leftShift($exp - 1);
159 
160  // Prevent overflow See: http://ethereum.stackexchange.com/a/7294/852.
161  $max_for_exp = $max_for_exp->subtract(new Math_BigInteger(1));
162 
163  if ($number->compare($max_for_exp) <= 0) {
164  $abi_l = $exp;
165  break;
166  }
167  }
168  if (!$abi_l) {
169  throw new \InvalidArgumentException('NOT IN RANGE: ' . $number->toString() . ' > (u)int256');
170  }
171  if ($negative) {
172  return 'int' . $abi_l;
173  }
174  else {
175  // Default to unsigned integer.
176  return 'uint' . $abi_l;
177  }
178  }
179 
193  protected function validateAbi($abi)
194  {
195  $abiObj = $this->splitAbi($this->abiAliases($abi));
196  $valid_length = in_array($abiObj->intLength, $this->getValidLengths());
197  $valid_type = in_array($abiObj->intType, $this->intTypes);
198  if (!($valid_length || $valid_type)) {
199  throw new \InvalidArgumentException('Can not validate ABI: ' . $abi);
200  }
201  return true;
202  }
203 
209  private static function abiAliases($abi)
210  {
211  // uint, int: synonyms for uint256, int256 respectively.
212  if ($abi === 'int') {
213  $abi = 'int256';
214  }
215  if ($abi === 'uint') {
216  $abi = 'uint256';
217  }
218  return $abi;
219  }
220 
224  protected static function splitAbi($abi)
225  {
226  $matches = [];
227  $valid = null;
228  // See: https://regex101.com/r/3XYumB/1
229  if (preg_match('/^(u?int)(\d{1,3})$/', self::abiAliases($abi),
230  $matches)) {
231  return (object)[
232  'abi' => $abi,
233  'intType' => $matches[1],
234  'intLength' => $matches[2],
235  ];
236  }
237  else {
238  throw new \InvalidArgumentException('Could not decode ABI for: ' . $abi);
239  }
240  }
241 
246  public function hexVal()
247  {
248 
249  // Ethereum requires two's complement.
250  // Math_BigInteger->toHex( [Boolean $twos_compliment = false])
251  $value = $this->value->toHex($this->value->is_negative);
252 
253  if (strlen($value) > self::HEXPADDING) {
254  throw new \Exception('Values > (u)int32 not supported yet: ' . $value);
255  }
256 
257  // Calc padding.
258  $pad = self::HEXPADDING - strlen($value);
259 
260  $fill = $this->value->is_negative ? 'f' : '0';
261  $ret = '0x' . str_repeat($fill, $pad) . $value;
262 
263  return $ret;
264  }
265 
270  public function hexValUnpadded()
271  {
272  return '0x' . $this->value->toHex($this->value->is_negative);
273  }
274 
278  public function getLength($abi)
279  {
280  $type = $this->splitAbi($abi);
281 
282  return $type->intLength;
283  }
284 
288  public function isNegative($abi = false)
289  {
290  if (!$abi) {
291  $type = $this->splitAbi($this->abi);
292  }
293  else {
294  $type = $abi;
295  }
296 
297  if ($type->abi === 'uint') {
298  return false;
299  }
300  else {
301  return true;
302  }
303  }
304 
314  public function isLargeNumber(Math_BigInteger $val)
315  {
316  return !((string)((int)$val->toString()) === $val->toString());
317  }
318 
326  public function val()
327  {
328  if ($this->isLargeNumber($this->value)) {
329  return $this->value->toString();
330  }
331  else {
332  return (int)$this->value->toString();
333  }
334  }
335 
339  public function encodedHexVal()
340  {
341  return $this->hexVal();
342  }
343 
349  public function isNotNull()
350  {
351  $null = new Math_BigInteger();
352  return ($this->value->compare($null) > 0 || $this->value->compare($null) < 0);
353  }
354 
362  public function getAbi()
363  {
364  return $this->abi;
365  }
366 
375  public static function getValidLengths()
376  {
377  $valid_lengths = "8;16;24;32;40;48;56;64;72;80;88;96;104;112;120;128;136;144;152;160;168;176;184;192;200;208;216;224;232;240;248;256";
378  return explode(';', $valid_lengths);
379  }
380 
384  public static function getDataLengthType()
385  {
386  return 'static';
387  }
388 }
getAbiFromNumber(Math_BigInteger $number)
Definition: EthQ.php:146
validate($val, array $params)
Definition: EthQ.php:61
static getDataLengthType()
Definition: EthQ.php:384
__construct($val, array $params=[])
Definition: EthQ.php:43
isLargeNumber(Math_BigInteger $val)
Definition: EthQ.php:314
static splitAbi($abi)
Definition: EthQ.php:224
isNegative($abi=false)
Definition: EthQ.php:288
static getValidLengths()
Definition: EthQ.php:375