Contract.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636
  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 blockchain\web3\src\Providers\Provider;
  13. use blockchain\web3\src\Providers\HttpProvider;
  14. use blockchain\web3\src\RequestManagers\RequestManager;
  15. use blockchain\web3\src\RequestManagers\HttpRequestManager;
  16. use blockchain\web3\src\Utils;
  17. use blockchain\web3\src\Eth;
  18. use blockchain\web3\src\Contracts\Ethabi;
  19. use blockchain\web3\src\Contracts\Types\Address;
  20. use blockchain\web3\src\Contracts\Types\Boolean;
  21. use blockchain\web3\src\Contracts\Types\Bytes;
  22. use blockchain\web3\src\Contracts\Types\DynamicBytes;
  23. use blockchain\web3\src\Contracts\Types\Integer;
  24. use blockchain\web3\src\Contracts\Types\Str;
  25. use blockchain\web3\src\Contracts\Types\Uinteger;
  26. use blockchain\web3\src\Validators\AddressValidator;
  27. use blockchain\web3\src\Validators\HexValidator;
  28. use blockchain\web3\src\Formatters\AddressFormatter;
  29. use blockchain\web3\src\Validators\StringValidator;
  30. class Contract
  31. {
  32. /**
  33. * provider
  34. *
  35. * @var \blockchain\web3\src\Providers\Provider
  36. */
  37. protected $provider;
  38. /**
  39. * abi
  40. *
  41. * @var array
  42. */
  43. protected $abi;
  44. /**
  45. * constructor
  46. *
  47. * @var array
  48. */
  49. protected $constructor = [];
  50. /**
  51. * functions
  52. *
  53. * @var array
  54. */
  55. protected $functions = [];
  56. /**
  57. * events
  58. *
  59. * @var array
  60. */
  61. protected $events = [];
  62. /**
  63. * toAddress
  64. *
  65. * @var string
  66. */
  67. protected $toAddress;
  68. /**
  69. * bytecode
  70. *
  71. * @var string
  72. */
  73. protected $bytecode;
  74. /**
  75. * eth
  76. *
  77. * @var \blockchain\web3\src\Eth
  78. */
  79. protected $eth;
  80. /**
  81. * ethabi
  82. *
  83. * @var \blockchain\web3\src\Contracts\Ethabi
  84. */
  85. protected $ethabi;
  86. /**
  87. * construct
  88. *
  89. * @param string|\blockchain\web3\src\Providers\Provider $provider
  90. * @param string|\stdClass|array $abi
  91. * @return void
  92. */
  93. public function __construct($provider, $abi)
  94. {
  95. if (is_string($provider) && (filter_var($provider, FILTER_VALIDATE_URL) !== false)) {
  96. // check the uri schema
  97. if (preg_match('/^https?:\/\//', $provider) === 1) {
  98. $requestManager = new HttpRequestManager($provider);
  99. $this->provider = new HttpProvider($requestManager);
  100. }
  101. } else if ($provider instanceof Provider) {
  102. $this->provider = $provider;
  103. }
  104. $abi = Utils::jsonToArray($abi, 5);
  105. foreach ($abi as $item) {
  106. if (isset($item['type'])) {
  107. if ($item['type'] === 'function') {
  108. $this->functions[$item['name']] = $item;
  109. } elseif ($item['type'] === 'constructor') {
  110. $this->constructor = $item;
  111. } elseif ($item['type'] === 'event') {
  112. $this->events[$item['name']] = $item;
  113. }
  114. }
  115. }
  116. $this->abi = $abi;
  117. $this->eth = new Eth($this->provider);
  118. $this->ethabi = new Ethabi([
  119. 'address' => new Address,
  120. 'bool' => new Boolean,
  121. 'bytes' => new Bytes,
  122. 'dynamicBytes' => new DynamicBytes,
  123. 'int' => new Integer,
  124. 'string' => new Str,
  125. 'uint' => new Uinteger,
  126. ]);
  127. }
  128. /**
  129. * call
  130. *
  131. * @param string $name
  132. * @param array $arguments
  133. * @return void
  134. */
  135. // public function __call($name, $arguments)
  136. // {
  137. // if (empty($this->provider)) {
  138. // throw new \RuntimeException('Please set provider first.');
  139. // }
  140. // $class = explode('\\', get_class());
  141. // if (preg_match('/^[a-zA-Z0-9]+$/', $name) === 1) {
  142. // }
  143. // }
  144. /**
  145. * get
  146. *
  147. * @param string $name
  148. * @return mixed
  149. */
  150. public function __get($name)
  151. {
  152. $method = 'get' . ucfirst($name);
  153. if (method_exists($this, $method)) {
  154. return call_user_func_array([$this, $method], []);
  155. }
  156. return false;
  157. }
  158. /**
  159. * set
  160. *
  161. * @param string $name
  162. * @param mixed $value
  163. * @return mixed
  164. */
  165. public function __set($name, $value)
  166. {
  167. $method = 'set' . ucfirst($name);
  168. if (method_exists($this, $method)) {
  169. return call_user_func_array([$this, $method], [$value]);
  170. }
  171. return false;
  172. }
  173. /**
  174. * getProvider
  175. *
  176. * @return \blockchain\web3\src\Providers\Provider
  177. */
  178. public function getProvider()
  179. {
  180. return $this->provider;
  181. }
  182. /**
  183. * setProvider
  184. *
  185. * @param \blockchain\web3\src\Providers\Provider $provider
  186. * @return $this
  187. */
  188. public function setProvider($provider)
  189. {
  190. if ($provider instanceof Provider) {
  191. $this->provider = $provider;
  192. }
  193. return $this;
  194. }
  195. /**
  196. * getFunctions
  197. *
  198. * @return array
  199. */
  200. public function getFunctions()
  201. {
  202. return $this->functions;
  203. }
  204. /**
  205. * getEvents
  206. *
  207. * @return array
  208. */
  209. public function getEvents()
  210. {
  211. return $this->events;
  212. }
  213. /**
  214. * @return string
  215. */
  216. public function getToAddress()
  217. {
  218. return $this->toAddress;
  219. }
  220. /**
  221. * getConstructor
  222. *
  223. * @return array
  224. */
  225. public function getConstructor()
  226. {
  227. return $this->constructor;
  228. }
  229. /**
  230. * getAbi
  231. *
  232. * @return array
  233. */
  234. public function getAbi()
  235. {
  236. return $this->abi;
  237. }
  238. /**
  239. * setAbi
  240. *
  241. * @param string $abi
  242. * @return $this
  243. */
  244. public function setAbi($abi)
  245. {
  246. return $this->abi($abi);
  247. }
  248. /**
  249. * getEthabi
  250. *
  251. * @return array
  252. */
  253. public function getEthabi()
  254. {
  255. return $this->ethabi;
  256. }
  257. /**
  258. * getEth
  259. *
  260. * @return \blockchain\web3\src\Eth
  261. */
  262. public function getEth()
  263. {
  264. return $this->eth;
  265. }
  266. /**
  267. * setBytecode
  268. *
  269. * @param string $bytecode
  270. * @return $this
  271. */
  272. public function setBytecode($bytecode)
  273. {
  274. return $this->bytecode($bytecode);
  275. }
  276. /**
  277. * setToAddress
  278. *
  279. * @param string $bytecode
  280. * @return $this
  281. */
  282. public function setToAddress($address)
  283. {
  284. return $this->at($address);
  285. }
  286. /**
  287. * at
  288. *
  289. * @param string $address
  290. * @return $this
  291. */
  292. public function at($address)
  293. {
  294. if (AddressValidator::validate($address) === false) {
  295. throw new InvalidArgumentException('Please make sure address is valid.');
  296. }
  297. $this->toAddress = AddressFormatter::format($address);
  298. return $this;
  299. }
  300. /**
  301. * bytecode
  302. *
  303. * @param string $bytecode
  304. * @return $this
  305. */
  306. public function bytecode($bytecode)
  307. {
  308. if (HexValidator::validate($bytecode) === false) {
  309. throw new InvalidArgumentException('Please make sure bytecode is valid.');
  310. }
  311. $this->bytecode = Utils::stripZero($bytecode);
  312. return $this;
  313. }
  314. /**
  315. * abi
  316. *
  317. * @param string $abi
  318. * @return $this
  319. */
  320. public function abi($abi)
  321. {
  322. if (StringValidator::validate($abi) === false) {
  323. throw new InvalidArgumentException('Please make sure abi is valid.');
  324. }
  325. $abi = Utils::jsonToArray($abi, 5);
  326. foreach ($abi as $item) {
  327. if (isset($item['type'])) {
  328. if ($item['type'] === 'function') {
  329. $this->functions[$item['name']] = $item;
  330. } elseif ($item['type'] === 'constructor') {
  331. $this->constructor = $item;
  332. } elseif ($item['type'] === 'event') {
  333. $this->events[$item['name']] = $item;
  334. }
  335. }
  336. }
  337. $this->abi = $abi;
  338. return $this;
  339. }
  340. /**
  341. * new
  342. * Deploy a contruct with params.
  343. *
  344. * @param mixed
  345. * @return void
  346. */
  347. public function new()
  348. {
  349. if (isset($this->constructor)) {
  350. $constructor = $this->constructor;
  351. $arguments = func_get_args();
  352. $callback = array_pop($arguments);
  353. if (count($arguments) < count($constructor['inputs'])) {
  354. throw new InvalidArgumentException('Please make sure you have put all constructor params and callback.');
  355. }
  356. if (is_callable($callback) !== true) {
  357. throw new \InvalidArgumentException('The last param must be callback function.');
  358. }
  359. if (!isset($this->bytecode)) {
  360. throw new \InvalidArgumentException('Please call bytecode($bytecode) before new().');
  361. }
  362. $params = array_splice($arguments, 0, count($constructor['inputs']));
  363. $data = $this->ethabi->encodeParameters($constructor, $params);
  364. $transaction = [];
  365. if (count($arguments) > 0) {
  366. $transaction = $arguments[0];
  367. }
  368. $transaction['data'] = '0x' . $this->bytecode . Utils::stripZero($data);
  369. $this->eth->sendTransaction($transaction, function ($err, $transaction) use ($callback) {
  370. if ($err !== null) {
  371. return call_user_func($callback, $err, null);
  372. }
  373. return call_user_func($callback, null, $transaction);
  374. });
  375. }
  376. }
  377. /**
  378. * send
  379. * Send function method.
  380. *
  381. * @param mixed
  382. * @return void
  383. */
  384. public function send()
  385. {
  386. if (isset($this->functions)) {
  387. $arguments = func_get_args();
  388. $method = array_splice($arguments, 0, 1)[0];
  389. $callback = array_pop($arguments);
  390. if (!is_string($method) || !isset($this->functions[$method])) {
  391. throw new InvalidArgumentException('Please make sure the method exists.');
  392. }
  393. $function = $this->functions[$method];
  394. if (count($arguments) < count($function['inputs'])) {
  395. throw new InvalidArgumentException('Please make sure you have put all function params and callback.');
  396. }
  397. if (is_callable($callback) !== true) {
  398. throw new \InvalidArgumentException('The last param must be callback function.');
  399. }
  400. $params = array_splice($arguments, 0, count($function['inputs']));
  401. $data = $this->ethabi->encodeParameters($function, $params);
  402. $functionName = Utils::jsonMethodToString($function);
  403. $functionSignature = $this->ethabi->encodeFunctionSignature($functionName);
  404. $transaction = [];
  405. if (count($arguments) > 0) {
  406. $transaction = $arguments[0];
  407. }
  408. $transaction['to'] = $this->toAddress;
  409. $transaction['data'] = $functionSignature . Utils::stripZero($data);
  410. $this->eth->sendTransaction($transaction, function ($err, $transaction) use ($callback) {
  411. if ($err !== null) {
  412. return call_user_func($callback, $err, null);
  413. }
  414. return call_user_func($callback, null, $transaction);
  415. });
  416. }
  417. }
  418. /**
  419. * call
  420. * Call function method.
  421. *
  422. * @param mixed
  423. * @return void
  424. */
  425. public function call()
  426. {
  427. if (isset($this->functions)) {
  428. $arguments = func_get_args();
  429. $method = array_splice($arguments, 0, 1)[0];
  430. $callback = array_pop($arguments);
  431. if (!is_string($method) || !isset($this->functions[$method])) {
  432. throw new InvalidArgumentException('Please make sure the method exists.');
  433. }
  434. $function = $this->functions[$method];
  435. if (count($arguments) < count($function['inputs'])) {
  436. throw new InvalidArgumentException('Please make sure you have put all function params and callback.');
  437. }
  438. if (is_callable($callback) !== true) {
  439. throw new \InvalidArgumentException('The last param must be callback function.');
  440. }
  441. $params = array_splice($arguments, 0, count($function['inputs']));
  442. $data = $this->ethabi->encodeParameters($function, $params);
  443. $functionName = Utils::jsonMethodToString($function);
  444. $functionSignature = $this->ethabi->encodeFunctionSignature($functionName);
  445. $transaction = [];
  446. if (count($arguments) > 0) {
  447. $transaction = $arguments[0];
  448. }
  449. $transaction['to'] = $this->toAddress;
  450. $transaction['data'] = $functionSignature . Utils::stripZero($data);
  451. $this->eth->call($transaction, function ($err, $transaction) use ($callback, $function) {
  452. if ($err !== null) {
  453. return call_user_func($callback, $err, null);
  454. }
  455. $decodedTransaction = $this->ethabi->decodeParameters($function, $transaction);
  456. return call_user_func($callback, null, $decodedTransaction);
  457. });
  458. }
  459. }
  460. /**
  461. * estimateGas
  462. * Estimate function gas.
  463. *
  464. * @param mixed
  465. * @return void
  466. */
  467. public function estimateGas()
  468. {
  469. if (isset($this->functions) || isset($this->constructor)) {
  470. $arguments = func_get_args();
  471. $callback = array_pop($arguments);
  472. if (empty($this->toAddress) && !empty($this->bytecode)) {
  473. $constructor = $this->constructor;
  474. if (count($arguments) < count($constructor['inputs'])) {
  475. throw new InvalidArgumentException('Please make sure you have put all constructor params and callback.');
  476. }
  477. if (is_callable($callback) !== true) {
  478. throw new \InvalidArgumentException('The last param must be callback function.');
  479. }
  480. if (!isset($this->bytecode)) {
  481. throw new \InvalidArgumentException('Please call bytecode($bytecode) before estimateGas().');
  482. }
  483. $params = array_splice($arguments, 0, count($constructor['inputs']));
  484. $data = $this->ethabi->encodeParameters($constructor, $params);
  485. $transaction = [];
  486. if (count($arguments) > 0) {
  487. $transaction = $arguments[0];
  488. }
  489. $transaction['to'] = '';
  490. $transaction['data'] = '0x' . $this->bytecode . Utils::stripZero($data);
  491. } else {
  492. $method = array_splice($arguments, 0, 1)[0];
  493. $from = array_splice($arguments, 0, 1)[0];
  494. if (!is_string($method) && !isset($this->functions[$method])) {
  495. throw new InvalidArgumentException('Please make sure the method is existed.');
  496. }
  497. $function = $this->functions[$method];
  498. if (count($arguments) < count($function['inputs'])) {
  499. throw new InvalidArgumentException('Please make sure you have put all function params and callback.');
  500. }
  501. if (is_callable($callback) !== true) {
  502. throw new \InvalidArgumentException('The last param must be callback function.');
  503. }
  504. $params = array_splice($arguments, 0, count($function['inputs']));
  505. $data = $this->ethabi->encodeParameters($function, $params);
  506. $functionName = Utils::jsonMethodToString($function);
  507. $functionSignature = $this->ethabi->encodeFunctionSignature($functionName);
  508. $transaction = [];
  509. if (count($arguments) > 0) {
  510. $transaction = $arguments[0];
  511. }
  512. $transaction['from'] = $from;
  513. $transaction['to'] = $this->toAddress;
  514. $transaction['data'] = $functionSignature . Utils::stripZero($data);
  515. }
  516. $this->eth->estimateGas($transaction, function ($err, $gas) use ($callback) {
  517. if ($err !== null) {
  518. return call_user_func($callback, $err, null);
  519. }
  520. return call_user_func($callback, null, $gas);
  521. });
  522. }
  523. }
  524. /**
  525. * getData
  526. * Get the function method call data.
  527. * With this function, you can send signed contract function transaction.
  528. * 1. Get the funtion data with params.
  529. * 2. Sign the data with user private key.
  530. * 3. Call sendRawTransaction.
  531. *
  532. * @param mixed
  533. * @return void
  534. */
  535. public function getData()
  536. {
  537. if (isset($this->functions) || isset($this->constructor)) {
  538. $arguments = func_get_args();
  539. $functionData = '';
  540. if (empty($this->toAddress) && !empty($this->bytecode)) {
  541. $constructor = $this->constructor;
  542. if (count($arguments) < count($constructor['inputs'])) {
  543. throw new InvalidArgumentException('Please make sure you have put all constructor params and callback.');
  544. }
  545. if (!isset($this->bytecode)) {
  546. throw new \InvalidArgumentException('Please call bytecode($bytecode) before getData().');
  547. }
  548. $params = array_splice($arguments, 0, count($constructor['inputs']));
  549. $data = $this->ethabi->encodeParameters($constructor, $params);
  550. $functionData = $this->bytecode . Utils::stripZero($data);
  551. } else {
  552. $method = array_splice($arguments, 0, 1)[0];
  553. if (!is_string($method) && !isset($this->functions[$method])) {
  554. throw new InvalidArgumentException('Please make sure the method is existed.');
  555. }
  556. $function = $this->functions[$method];
  557. if (count($arguments) < count($function['inputs'])) {
  558. throw new InvalidArgumentException('Please make sure you have put all function params and callback.');
  559. }
  560. $params = array_splice($arguments, 0, count($function['inputs']));
  561. $data = $this->ethabi->encodeParameters($function, $params);
  562. $functionName = Utils::jsonMethodToString($function);
  563. $functionSignature = $this->ethabi->encodeFunctionSignature($functionName);
  564. $functionData = Utils::stripZero($functionSignature) . Utils::stripZero($data);
  565. }
  566. return $functionData;
  567. }
  568. }
  569. }