Ethabi.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  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 qiniu\services\blockchain\bsc\src\Contracts;
  11. use InvalidArgumentException;
  12. use stdClass;
  13. use qiniu\services\blockchain\bsc\src\Utils;
  14. use qiniu\services\blockchain\bsc\src\Formatters\IntegerFormatter;
  15. class Ethabi
  16. {
  17. /**
  18. * types
  19. *
  20. * @var array
  21. */
  22. protected $types = [];
  23. /**
  24. * construct
  25. *
  26. * @param array $types
  27. * @return void
  28. */
  29. public function __construct($types=[])
  30. {
  31. if (!is_array($types)) {
  32. $types = [];
  33. }
  34. $this->types = $types;
  35. }
  36. /**
  37. * get
  38. *
  39. * @param string $name
  40. * @return mixed
  41. */
  42. public function __get($name)
  43. {
  44. $method = 'get' . ucfirst($name);
  45. if (method_exists($this, $method)) {
  46. return call_user_func_array([$this, $method], []);
  47. }
  48. return false;
  49. }
  50. /**
  51. * set
  52. *
  53. * @param string $name
  54. * @param mixed $value
  55. * @return mixed
  56. */
  57. public function __set($name, $value)
  58. {
  59. $method = 'set' . ucfirst($name);
  60. if (method_exists($this, $method)) {
  61. return call_user_func_array([$this, $method], [$value]);
  62. }
  63. return false;
  64. }
  65. /**
  66. * callStatic
  67. *
  68. * @param string $name
  69. * @param array $arguments
  70. * @return void
  71. */
  72. public static function __callStatic($name, $arguments)
  73. {
  74. //
  75. }
  76. /**
  77. * encodeFunctionSignature
  78. *
  79. * @param string|stdClass|array $functionName
  80. * @return string
  81. */
  82. public function encodeFunctionSignature($functionName)
  83. {
  84. if (!is_string($functionName)) {
  85. $functionName = Utils::jsonMethodToString($functionName);
  86. }
  87. return mb_substr(Utils::sha3($functionName), 0, 10);
  88. }
  89. /**
  90. * encodeEventSignature
  91. *
  92. * @param string|stdClass|array $functionName
  93. * @return string
  94. */
  95. public function encodeEventSignature($functionName)
  96. {
  97. if (!is_string($functionName)) {
  98. $functionName = Utils::jsonMethodToString($functionName);
  99. }
  100. return Utils::sha3($functionName);
  101. }
  102. /**
  103. * encodeParameter
  104. *
  105. * @param string $type
  106. * @param mixed $param
  107. * @return string
  108. */
  109. public function encodeParameter($type, $param)
  110. {
  111. if (!is_string($type)) {
  112. throw new InvalidArgumentException('The type to encodeParameter must be string.');
  113. }
  114. return $this->encodeParameters([$type], [$param]);
  115. }
  116. /**
  117. * encodeParameters
  118. *
  119. * @param stdClass|array $types
  120. * @param array $params
  121. * @return string
  122. */
  123. public function encodeParameters($types, $params)
  124. {
  125. // change json to array
  126. if ($types instanceof stdClass && isset($types->inputs)) {
  127. $types = Utils::jsonToArray($types, 2);
  128. }
  129. if (is_array($types) && isset($types['inputs'])) {
  130. $inputTypes = $types;
  131. $types = [];
  132. foreach ($inputTypes['inputs'] as $input) {
  133. if (isset($input['type'])) {
  134. $types[] = $input['type'];
  135. }
  136. }
  137. }
  138. if (count($types) !== count($params)) {
  139. throw new InvalidArgumentException('encodeParameters number of types must equal to number of params.');
  140. }
  141. $typesLength = count($types);
  142. $solidityTypes = $this->getSolidityTypes($types);
  143. foreach ($types as $key => $type) {
  144. $match = [];
  145. if (preg_match('/^([a-zA-Z]+)/', $type, $match) === 1) {
  146. if (isset($this->types[$match[0]])) {
  147. $className = $this->types[$match[0]];
  148. if (call_user_func([$this->types[$match[0]], 'isType'], $type) === false) {
  149. throw new InvalidArgumentException('Unsupport solidity parameter type: ' . $type);
  150. }
  151. $solidityTypes[$key] = $className;
  152. }
  153. }
  154. }
  155. $encodes = array_fill(0, $typesLength, '');
  156. foreach ($solidityTypes as $key => $type) {
  157. $encodes[$key] = call_user_func([$type, 'encode'], $params[$key], $types[$key]);
  158. }
  159. $dynamicOffset = 0;
  160. foreach ($solidityTypes as $key => $type) {
  161. $staticPartLength = $type->staticPartLength($types[$key]);
  162. $roundedStaticPartLength = floor(($staticPartLength + 31) / 32) * 32;
  163. if ($type->isDynamicType($types[$key]) || $type->isDynamicArray($types[$key])) {
  164. $dynamicOffset += 32;
  165. } else {
  166. $dynamicOffset += $roundedStaticPartLength;
  167. }
  168. }
  169. return '0x' . $this->encodeMultiWithOffset($types, $solidityTypes, $encodes, $dynamicOffset);
  170. }
  171. /**
  172. * decodeParameter
  173. *
  174. * @param string $type
  175. * @param mixed $param
  176. * @return string
  177. */
  178. public function decodeParameter($type, $param)
  179. {
  180. if (!is_string($type)) {
  181. throw new InvalidArgumentException('The type to decodeParameter must be string.');
  182. }
  183. return $this->decodeParameters([$type], $param)[0];
  184. }
  185. /**
  186. * decodeParameters
  187. *
  188. * @param stdClass|array $type
  189. * @param string $param
  190. * @return string
  191. */
  192. public function decodeParameters($types, $param)
  193. {
  194. if (!is_string($param)) {
  195. throw new InvalidArgumentException('The type or param to decodeParameters must be string.');
  196. }
  197. // change json to array
  198. if ($types instanceof stdClass && isset($types->outputs)) {
  199. $types = Utils::jsonToArray($types, 2);
  200. }
  201. if (is_array($types) && isset($types['outputs'])) {
  202. $outputTypes = $types;
  203. $types = [];
  204. foreach ($outputTypes['outputs'] as $output) {
  205. if (isset($output['type'])) {
  206. $types[] = $output['type'];
  207. }
  208. }
  209. }
  210. $typesLength = count($types);
  211. $solidityTypes = $this->getSolidityTypes($types);
  212. $offsets = array_fill(0, $typesLength, 0);
  213. for ($i=0; $i<$typesLength; $i++) {
  214. $offsets[$i] = $solidityTypes[$i]->staticPartLength($types[$i]);
  215. }
  216. for ($i=1; $i<$typesLength; $i++) {
  217. $offsets[$i] += $offsets[$i - 1];
  218. }
  219. for ($i=0; $i<$typesLength; $i++) {
  220. $offsets[$i] -= $solidityTypes[$i]->staticPartLength($types[$i]);
  221. }
  222. $result = [];
  223. $param = mb_strtolower(Utils::stripZero($param));
  224. for ($i=0; $i<$typesLength; $i++) {
  225. if (isset($outputTypes['outputs'][$i]['name']) && empty($outputTypes['outputs'][$i]['name']) === false) {
  226. $result[$outputTypes['outputs'][$i]['name']] = $solidityTypes[$i]->decode($param, $offsets[$i], $types[$i]);
  227. } else {
  228. $result[$i] = $solidityTypes[$i]->decode($param, $offsets[$i], $types[$i]);
  229. }
  230. }
  231. return $result;
  232. }
  233. /**
  234. * getSolidityTypes
  235. *
  236. * @param array $types
  237. * @return array
  238. */
  239. protected function getSolidityTypes($types)
  240. {
  241. if (!is_array($types)) {
  242. throw new InvalidArgumentException('Types must be array');
  243. }
  244. $solidityTypes = array_fill(0, count($types), 0);
  245. foreach ($types as $key => $type) {
  246. $match = [];
  247. if (preg_match('/^([a-zA-Z]+)/', $type, $match) === 1) {
  248. if (isset($this->types[$match[0]])) {
  249. $className = $this->types[$match[0]];
  250. if (call_user_func([$this->types[$match[0]], 'isType'], $type) === false) {
  251. // check dynamic bytes
  252. if ($match[0] === 'bytes') {
  253. $className = $this->types['dynamicBytes'];
  254. } else {
  255. throw new InvalidArgumentException('Unsupport solidity parameter type: ' . $type);
  256. }
  257. }
  258. $solidityTypes[$key] = $className;
  259. }
  260. }
  261. }
  262. return $solidityTypes;
  263. }
  264. /**
  265. * encodeWithOffset
  266. *
  267. * @param string $type
  268. * @param \qiniu\services\blockchain\bsc\src\Contracts\SolidityType $solidityType
  269. * @param mixed $encode
  270. * @param int $offset
  271. * @return string
  272. */
  273. protected function encodeWithOffset($type, $solidityType, $encoded, $offset)
  274. {
  275. if ($solidityType->isDynamicArray($type)) {
  276. $nestedName = $solidityType->nestedName($type);
  277. $nestedStaticPartLength = $solidityType->staticPartLength($type);
  278. $result = $encoded[0];
  279. if ($solidityType->isDynamicArray($nestedName)) {
  280. $previousLength = 2;
  281. for ($i=0; $i<count($encoded); $i++) {
  282. if (isset($encoded[$i - 1])) {
  283. $previousLength += abs($encoded[$i - 1][0]);
  284. }
  285. $result .= IntegerFormatter::format($offset + $i * $nestedStaticPartLength + $previousLength * 32);
  286. }
  287. }
  288. for ($i=0; $i<count($encoded); $i++) {
  289. // $bn = Utils::toBn($result);
  290. // $divided = $bn->divide(Utils::toBn(2));
  291. // if (is_array($divided)) {
  292. // $additionalOffset = (int) $divided[0]->toString();
  293. // } else {
  294. // $additionalOffset = 0;
  295. // }
  296. $additionalOffset = floor(mb_strlen($result) / 2);
  297. $result .= $this->encodeWithOffset($nestedName, $solidityType, $encoded[$i], $offset + $additionalOffset);
  298. }
  299. return mb_substr($result, 64);
  300. } elseif ($solidityType->isStaticArray($type)) {
  301. $nestedName = $solidityType->nestedName($type);
  302. $nestedStaticPartLength = $solidityType->staticPartLength($type);
  303. $result = '';
  304. if ($solidityType->isDynamicArray($nestedName)) {
  305. $previousLength = 0;
  306. for ($i=0; $i<count($encoded); $i++) {
  307. if (isset($encoded[$i - 1])) {
  308. $previousLength += abs($encoded[$i - 1])[0];
  309. }
  310. $result .= IntegerFormatter::format($offset + $i * $nestedStaticPartLength + $previousLength * 32);
  311. }
  312. }
  313. for ($i=0; $i<count($encoded); $i++) {
  314. // $bn = Utils::toBn($result);
  315. // $divided = $bn->divide(Utils::toBn(2));
  316. // if (is_array($divided)) {
  317. // $additionalOffset = (int) $divided[0]->toString();
  318. // } else {
  319. // $additionalOffset = 0;
  320. // }
  321. $additionalOffset = floor(mb_strlen($result) / 2);
  322. $result .= $this->encodeWithOffset($nestedName, $solidityType, $encoded[$i], $offset + $additionalOffset);
  323. }
  324. return $result;
  325. }
  326. return $encoded;
  327. }
  328. /**
  329. * encodeMultiWithOffset
  330. *
  331. * @param array $types
  332. * @param array $solidityTypes
  333. * @param array $encodes
  334. * @param int $dynamicOffset
  335. * @return string
  336. */
  337. protected function encodeMultiWithOffset($types, $solidityTypes, $encodes, $dynamicOffset)
  338. {
  339. $result = '';
  340. foreach ($solidityTypes as $key => $type) {
  341. if ($type->isDynamicType($types[$key]) || $type->isDynamicArray($types[$key])) {
  342. $result .= IntegerFormatter::format($dynamicOffset);
  343. $e = $this->encodeWithOffset($types[$key], $type, $encodes[$key], $dynamicOffset);
  344. $dynamicOffset += floor(mb_strlen($e) / 2);
  345. } else {
  346. $result .= $this->encodeWithOffset($types[$key], $type, $encodes[$key], $dynamicOffset);
  347. }
  348. }
  349. foreach ($solidityTypes as $key => $type) {
  350. if ($type->isDynamicType($types[$key]) || $type->isDynamicArray($types[$key])) {
  351. $e = $this->encodeWithOffset($types[$key], $type, $encodes[$key], $dynamicOffset);
  352. // $dynamicOffset += floor(mb_strlen($e) / 2);
  353. $result .= $e;
  354. }
  355. }
  356. return $result;
  357. }
  358. }