Utils.php 16 KB

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