Utils.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. <?php
  2. /**
  3. * This file is part of web3.php package.
  4. *
  5. * (c) Kuan-Cheng,Lai <alk03073135@gmail.com>
  6. *
  7. * @author Peter Lai <alk03073135@gmail.com>
  8. * @license MIT
  9. */
  10. namespace Web3;
  11. use RuntimeException;
  12. use InvalidArgumentException;
  13. use stdClass;
  14. use kornrunner\Keccak;
  15. use phpseclib\Math\BigInteger as BigNumber;
  16. class Utils
  17. {
  18. /**
  19. * SHA3_NULL_HASH
  20. *
  21. * @const string
  22. */
  23. const SHA3_NULL_HASH = 'c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470';
  24. /**
  25. * UNITS
  26. * from ethjs-unit
  27. *
  28. * @const array
  29. */
  30. const UNITS = [
  31. 'noether' => '0',
  32. 'wei' => '1',
  33. 'kwei' => '1000',
  34. 'Kwei' => '1000',
  35. 'babbage' => '1000',
  36. 'femtoether' => '1000',
  37. 'mwei' => '1000000',
  38. 'Mwei' => '1000000',
  39. 'lovelace' => '1000000',
  40. 'picoether' => '1000000',
  41. 'gwei' => '1000000000',
  42. 'Gwei' => '1000000000',
  43. 'shannon' => '1000000000',
  44. 'nanoether' => '1000000000',
  45. 'nano' => '1000000000',
  46. 'szabo' => '1000000000000',
  47. 'microether' => '1000000000000',
  48. 'micro' => '1000000000000',
  49. 'finney' => '1000000000000000',
  50. 'milliether' => '1000000000000000',
  51. 'milli' => '1000000000000000',
  52. 'ether' => '1000000000000000000',
  53. 'kether' => '1000000000000000000000',
  54. 'grand' => '1000000000000000000000',
  55. 'mether' => '1000000000000000000000000',
  56. 'gether' => '1000000000000000000000000000',
  57. 'tether' => '1000000000000000000000000000000'
  58. ];
  59. /**
  60. * NEGATIVE1
  61. * Cannot work, see: http://php.net/manual/en/language.constants.syntax.php
  62. *
  63. * @const
  64. */
  65. // const NEGATIVE1 = new BigNumber(-1);
  66. /**
  67. * construct
  68. *
  69. * @return void
  70. */
  71. // public function __construct() {}
  72. /**
  73. * toHex
  74. * Encoding string or integer or numeric string(is not zero prefixed) or big number to hex.
  75. *
  76. * @param string|int|BigNumber $value
  77. * @param bool $isPrefix
  78. * @return string
  79. */
  80. public static function toHex($value, $isPrefix=false)
  81. {
  82. if (is_int($value) || is_float($value)) {
  83. // turn to hex number
  84. $bn = self::toBn($value);
  85. $hex = $bn->toHex(true);
  86. $hex = preg_replace('/^0+(?!$)/', '', $hex);
  87. } elseif (is_string($value)) {
  88. $value = self::stripZero($value);
  89. $hex = implode('', unpack('H*', $value));
  90. } elseif ($value instanceof BigNumber) {
  91. $hex = $value->toHex(true);
  92. $hex = preg_replace('/^0+(?!$)/', '', $hex);
  93. } else {
  94. throw new InvalidArgumentException('The value to toHex function is not support.');
  95. }
  96. if ($isPrefix) {
  97. return '0x' . $hex;
  98. }
  99. return $hex;
  100. }
  101. /**
  102. * hexToBin
  103. *
  104. * @param string
  105. * @return string
  106. */
  107. public static function hexToBin($value)
  108. {
  109. if (!is_string($value)) {
  110. throw new InvalidArgumentException('The value to hexToBin function must be string.');
  111. }
  112. if (self::isZeroPrefixed($value)) {
  113. $count = 1;
  114. $value = str_replace('0x', '', $value, $count);
  115. // avoid suffix 0
  116. if (strlen($value) % 2 > 0) {
  117. $value = '0' . $value;
  118. }
  119. }
  120. return pack('H*', $value);
  121. }
  122. /**
  123. * isZeroPrefixed
  124. *
  125. * @param string
  126. * @return bool
  127. */
  128. public static function isZeroPrefixed($value)
  129. {
  130. if (!is_string($value)) {
  131. throw new InvalidArgumentException('The value to isZeroPrefixed function must be string.');
  132. }
  133. return (strpos($value, '0x') === 0);
  134. }
  135. /**
  136. * stripZero
  137. *
  138. * @param string $value
  139. * @return string
  140. */
  141. public static function stripZero($value)
  142. {
  143. if (self::isZeroPrefixed($value)) {
  144. $count = 1;
  145. return str_replace('0x', '', $value, $count);
  146. }
  147. return $value;
  148. }
  149. /**
  150. * isNegative
  151. *
  152. * @param string
  153. * @return bool
  154. */
  155. public static function isNegative($value)
  156. {
  157. if (!is_string($value)) {
  158. throw new InvalidArgumentException('The value to isNegative function must be string.');
  159. }
  160. return (strpos($value, '-') === 0);
  161. }
  162. /**
  163. * isAddress
  164. *
  165. * @param string $value
  166. * @return bool
  167. */
  168. public static function isAddress($value)
  169. {
  170. if (!is_string($value)) {
  171. throw new InvalidArgumentException('The value to isAddress function must be string.');
  172. }
  173. if (preg_match('/^(0x|0X)?[a-f0-9A-F]{40}$/', $value) !== 1) {
  174. return false;
  175. } elseif (preg_match('/^(0x|0X)?[a-f0-9]{40}$/', $value) === 1 || preg_match('/^(0x|0X)?[A-F0-9]{40}$/', $value) === 1) {
  176. return true;
  177. }
  178. return self::isAddressChecksum($value);
  179. }
  180. /**
  181. * isAddressChecksum
  182. *
  183. * @param string $value
  184. * @return bool
  185. */
  186. public static function isAddressChecksum($value)
  187. {
  188. if (!is_string($value)) {
  189. throw new InvalidArgumentException('The value to isAddressChecksum function must be string.');
  190. }
  191. $value = self::stripZero($value);
  192. $hash = self::stripZero(self::sha3(mb_strtolower($value)));
  193. for ($i = 0; $i < 40; $i++) {
  194. if (
  195. (intval($hash[$i], 16) > 7 && mb_strtoupper($value[$i]) !== $value[$i]) ||
  196. (intval($hash[$i], 16) <= 7 && mb_strtolower($value[$i]) !== $value[$i])
  197. ) {
  198. return false;
  199. }
  200. }
  201. return true;
  202. }
  203. /**
  204. * toChecksumAddress
  205. *
  206. * @param string $value
  207. * @return string
  208. */
  209. public static function toChecksumAddress($value)
  210. {
  211. if (!is_string($value)) {
  212. throw new InvalidArgumentException('The value to toChecksumAddress function must be string.');
  213. }
  214. $value = self::stripZero(strtolower($value));
  215. $hash = self::stripZero(self::sha3($value));
  216. $ret = '0x';
  217. for ($i = 0; $i < 40; $i++) {
  218. if (intval($hash[$i], 16) >= 8) {
  219. $ret .= strtoupper($value[$i]);
  220. } else {
  221. $ret .= $value[$i];
  222. }
  223. }
  224. return $ret;
  225. }
  226. /**
  227. * isHex
  228. *
  229. * @param string $value
  230. * @return bool
  231. */
  232. public static function isHex($value)
  233. {
  234. return (is_string($value) && preg_match('/^(0x)?[a-f0-9]*$/', $value) === 1);
  235. }
  236. /**
  237. * sha3
  238. * keccak256
  239. *
  240. * @param string $value
  241. * @return string
  242. */
  243. public static function sha3($value)
  244. {
  245. if (!is_string($value)) {
  246. throw new InvalidArgumentException('The value to sha3 function must be string.');
  247. }
  248. if (strpos($value, '0x') === 0) {
  249. $value = self::hexToBin($value);
  250. }
  251. $hash = Keccak::hash($value, 256);
  252. if ($hash === self::SHA3_NULL_HASH) {
  253. return null;
  254. }
  255. return '0x' . $hash;
  256. }
  257. /**
  258. * toString
  259. *
  260. * @param mixed $value
  261. * @return string
  262. */
  263. public static function toString($value)
  264. {
  265. $value = (string) $value;
  266. return $value;
  267. }
  268. /**
  269. * toWei
  270. * Change number from unit to wei.
  271. * For example:
  272. * $wei = Utils::toWei('1', 'kwei');
  273. * $wei->toString(); // 1000
  274. *
  275. * @param BigNumber|string $number
  276. * @param string $unit
  277. * @return \phpseclib\Math\BigInteger
  278. */
  279. public static function toWei($number, $unit)
  280. {
  281. if (!is_string($number) && !($number instanceof BigNumber)) {
  282. throw new InvalidArgumentException('toWei number must be string or bignumber.');
  283. }
  284. $bn = self::toBn($number);
  285. if (!is_string($unit)) {
  286. throw new InvalidArgumentException('toWei unit must be string.');
  287. }
  288. if (!isset(self::UNITS[$unit])) {
  289. throw new InvalidArgumentException('toWei doesn\'t support ' . $unit . ' unit.');
  290. }
  291. $bnt = new BigNumber(self::UNITS[$unit]);
  292. if (is_array($bn)) {
  293. // fraction number
  294. list($whole, $fraction, $fractionLength, $negative1) = $bn;
  295. if ($fractionLength > strlen(self::UNITS[$unit])) {
  296. throw new InvalidArgumentException('toWei fraction part is out of limit.');
  297. }
  298. $whole = $whole->multiply($bnt);
  299. // There is no pow function in phpseclib 2.0, only can see in dev-master
  300. // Maybe implement own biginteger in the future
  301. // See 2.0 BigInteger: https://github.com/phpseclib/phpseclib/blob/2.0/phpseclib/Math/BigInteger.php
  302. // See dev-master BigInteger: https://github.com/phpseclib/phpseclib/blob/master/phpseclib/Math/BigInteger.php#L700
  303. // $base = (new BigNumber(10))->pow(new BigNumber($fractionLength));
  304. // So we switch phpseclib special global param, change in the future
  305. switch (MATH_BIGINTEGER_MODE) {
  306. case $whole::MODE_GMP:
  307. static $two;
  308. $powerBase = gmp_pow(gmp_init(10), (int) $fractionLength);
  309. break;
  310. case $whole::MODE_BCMATH:
  311. $powerBase = bcpow('10', (string) $fractionLength, 0);
  312. break;
  313. default:
  314. $powerBase = pow(10, (int) $fractionLength);
  315. break;
  316. }
  317. $base = new BigNumber($powerBase);
  318. $fraction = $fraction->multiply($bnt)->divide($base)[0];
  319. if ($negative1 !== false) {
  320. return $whole->add($fraction)->multiply($negative1);
  321. }
  322. return $whole->add($fraction);
  323. }
  324. return $bn->multiply($bnt);
  325. }
  326. /**
  327. * toEther
  328. * Change number from unit to ether.
  329. * For example:
  330. * list($bnq, $bnr) = Utils::toEther('1', 'kether');
  331. * $bnq->toString(); // 1000
  332. *
  333. * @param BigNumber|string|int $number
  334. * @param string $unit
  335. * @return array
  336. */
  337. public static function toEther($number, $unit)
  338. {
  339. // if ($unit === 'ether') {
  340. // throw new InvalidArgumentException('Please use another unit.');
  341. // }
  342. $wei = self::toWei($number, $unit);
  343. $bnt = new BigNumber(self::UNITS['ether']);
  344. return $wei->divide($bnt);
  345. }
  346. /**
  347. * fromWei
  348. * Change number from wei to unit.
  349. * For example:
  350. * list($bnq, $bnr) = Utils::fromWei('1000', 'kwei');
  351. * $bnq->toString(); // 1
  352. *
  353. * @param BigNumber|string|int $number
  354. * @param string $unit
  355. * @return \phpseclib\Math\BigInteger
  356. */
  357. public static function fromWei($number, $unit)
  358. {
  359. $bn = self::toBn($number);
  360. if (!is_string($unit)) {
  361. throw new InvalidArgumentException('fromWei unit must be string.');
  362. }
  363. if (!isset(self::UNITS[$unit])) {
  364. throw new InvalidArgumentException('fromWei doesn\'t support ' . $unit . ' unit.');
  365. }
  366. $bnt = new BigNumber(self::UNITS[$unit]);
  367. return $bn->divide($bnt);
  368. }
  369. /**
  370. * jsonMethodToString
  371. *
  372. * @param stdClass|array $json
  373. * @return string
  374. */
  375. public static function jsonMethodToString($json)
  376. {
  377. if ($json instanceof stdClass) {
  378. // one way to change whole json stdClass to array type
  379. // $jsonString = json_encode($json);
  380. // if (JSON_ERROR_NONE !== json_last_error()) {
  381. // throw new InvalidArgumentException('json_decode error: ' . json_last_error_msg());
  382. // }
  383. // $json = json_decode($jsonString, true);
  384. // another way to change whole json to array type but need the depth
  385. // $json = self::jsonToArray($json, $depth)
  386. // another way to change json to array type but not whole json stdClass
  387. $json = (array) $json;
  388. $typeName = [];
  389. foreach ($json['inputs'] as $param) {
  390. if (isset($param->type)) {
  391. $typeName[] = $param->type;
  392. }
  393. }
  394. return $json['name'] . '(' . implode(',', $typeName) . ')';
  395. } elseif (!is_array($json)) {
  396. throw new InvalidArgumentException('jsonMethodToString json must be array or stdClass.');
  397. }
  398. if (isset($json['name']) && strpos($json['name'], '(') > 0) {
  399. return $json['name'];
  400. }
  401. $typeName = [];
  402. foreach ($json['inputs'] as $param) {
  403. if (isset($param['type'])) {
  404. $typeName[] = $param['type'];
  405. }
  406. }
  407. return $json['name'] . '(' . implode(',', $typeName) . ')';
  408. }
  409. /**
  410. * jsonToArray
  411. *
  412. * @param stdClass|array $json
  413. * @return array
  414. */
  415. public static function jsonToArray($json)
  416. {
  417. if ($json instanceof stdClass) {
  418. $json = (array) $json;
  419. $typeName = [];
  420. foreach ($json as $key => $param) {
  421. if (is_array($param)) {
  422. foreach ($param as $subKey => $subParam) {
  423. $json[$key][$subKey] = self::jsonToArray($subParam);
  424. }
  425. } elseif ($param instanceof stdClass) {
  426. $json[$key] = self::jsonToArray($param);
  427. }
  428. }
  429. } elseif (is_array($json)) {
  430. foreach ($json as $key => $param) {
  431. if (is_array($param)) {
  432. foreach ($param as $subKey => $subParam) {
  433. $json[$key][$subKey] = self::jsonToArray($subParam);
  434. }
  435. } elseif ($param instanceof stdClass) {
  436. $json[$key] = self::jsonToArray($param);
  437. }
  438. }
  439. }
  440. return $json;
  441. }
  442. /**
  443. * toBn
  444. * Change number or number string to bignumber.
  445. *
  446. * @param BigNumber|string|int $number
  447. * @return array|\phpseclib\Math\BigInteger
  448. */
  449. public static function toBn($number)
  450. {
  451. if ($number instanceof BigNumber){
  452. $bn = $number;
  453. } elseif (is_int($number)) {
  454. $bn = new BigNumber($number);
  455. } elseif (is_numeric($number)) {
  456. $number = (string) $number;
  457. if (self::isNegative($number)) {
  458. $count = 1;
  459. $number = str_replace('-', '', $number, $count);
  460. $negative1 = new BigNumber(-1);
  461. }
  462. if (strpos($number, '.') > 0) {
  463. $comps = explode('.', $number);
  464. if (count($comps) > 2) {
  465. throw new InvalidArgumentException('toBn number must be a valid number.');
  466. }
  467. $whole = $comps[0];
  468. $fraction = $comps[1];
  469. return [
  470. new BigNumber($whole),
  471. new BigNumber($fraction),
  472. strlen($comps[1]),
  473. isset($negative1) ? $negative1 : false
  474. ];
  475. } else {
  476. $bn = new BigNumber($number);
  477. }
  478. if (isset($negative1)) {
  479. $bn = $bn->multiply($negative1);
  480. }
  481. } elseif (is_string($number)) {
  482. $number = mb_strtolower($number);
  483. if (self::isNegative($number)) {
  484. $count = 1;
  485. $number = str_replace('-', '', $number, $count);
  486. $negative1 = new BigNumber(-1);
  487. }
  488. if (self::isZeroPrefixed($number) || preg_match('/^[0-9a-f]+$/i', $number) === 1) {
  489. $number = self::stripZero($number);
  490. $bn = new BigNumber($number, 16);
  491. } elseif (empty($number)) {
  492. $bn = new BigNumber(0);
  493. } else {
  494. throw new InvalidArgumentException('toBn number must be valid hex string.');
  495. }
  496. if (isset($negative1)) {
  497. $bn = $bn->multiply($negative1);
  498. }
  499. } else {
  500. throw new InvalidArgumentException('toBn number must be BigNumber, string or int.');
  501. }
  502. return $bn;
  503. }
  504. /**
  505. * hexToNumber
  506. *
  507. * @param string $hexNumber
  508. * @return int
  509. */
  510. public static function hexToNumber($hexNumber)
  511. {
  512. if (!self::isZeroPrefixed($hexNumber)) {
  513. $hexNumber = '0x' . $hexNumber;
  514. }
  515. return intval(self::toBn($hexNumber)->toString());
  516. }
  517. }