method.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. /* eslint-disable no-control-regex */
  2. import utils from '../../utils/index.js';
  3. import { encodeParamsV2ByABI, decodeParamsV2ByABI } from '../../utils/abi.js';
  4. import { sha3 } from '../../utils/crypto.js';
  5. const getFunctionSelector = (abi) => {
  6. if ('stateMutability' in abi) {
  7. abi.stateMutability = abi.stateMutability ? abi.stateMutability.toLowerCase() : 'nonpayable';
  8. }
  9. abi.type = abi.type ? abi.type.toLowerCase() : '';
  10. if (abi.type === 'fallback' || abi.type === 'receive')
  11. return '0x';
  12. const iface = new utils.ethersUtils.Interface([abi]);
  13. let obj;
  14. if (abi.type === 'event') {
  15. obj = iface.getEvent(abi.name);
  16. }
  17. else {
  18. obj = iface.getFunction(abi.name);
  19. }
  20. if (obj) {
  21. return obj.format('sighash');
  22. }
  23. throw new Error('unknown function');
  24. };
  25. const decodeOutput = (abi, output) => {
  26. return decodeParamsV2ByABI(abi, output);
  27. };
  28. export class Method {
  29. tronWeb;
  30. contract;
  31. abi;
  32. name;
  33. inputs;
  34. outputs;
  35. functionSelector;
  36. signature;
  37. defaultOptions;
  38. constructor(contract, abi) {
  39. this.tronWeb = contract.tronWeb;
  40. this.contract = contract;
  41. this.abi = abi;
  42. this.name = abi.name || abi.type;
  43. this.inputs = abi.inputs || [];
  44. this.outputs = [];
  45. if ('outputs' in abi && abi.outputs) {
  46. this.outputs = abi.outputs;
  47. }
  48. this.functionSelector = getFunctionSelector(abi);
  49. this.signature = sha3(this.functionSelector, false).slice(0, 8);
  50. this.defaultOptions = {
  51. feeLimit: this.tronWeb.feeLimit,
  52. callValue: 0,
  53. userFeePercentage: 100,
  54. shouldPollResponse: false, // Only used for sign()
  55. };
  56. }
  57. decodeInput(data) {
  58. const abi = JSON.parse(JSON.stringify(this.abi));
  59. abi.outputs = abi.inputs;
  60. return decodeOutput(abi, '0x' + data);
  61. }
  62. onMethod(...args) {
  63. let rawParameter = '';
  64. if (this.abi && !/event/i.test(this.abi.type)) {
  65. rawParameter = encodeParamsV2ByABI(this.abi, args);
  66. }
  67. return {
  68. call: async (options = {}) => {
  69. options = {
  70. ...options,
  71. rawParameter,
  72. };
  73. return await this._call([], [], options);
  74. },
  75. send: async (options = {}, privateKey = this.tronWeb.defaultPrivateKey) => {
  76. options = {
  77. ...options,
  78. rawParameter,
  79. };
  80. return await this._send([], [], options, privateKey);
  81. },
  82. };
  83. }
  84. async _call(types, args, options = {}) {
  85. if (types.length !== args.length) {
  86. throw new Error('Invalid argument count provided');
  87. }
  88. if (!this.contract.address) {
  89. throw new Error('Smart contract is missing address');
  90. }
  91. if (!this.contract.deployed) {
  92. throw new Error('Calling smart contracts requires you to load the contract first');
  93. }
  94. if ('stateMutability' in this.abi) {
  95. const { stateMutability } = this.abi;
  96. if (stateMutability && !['pure', 'view'].includes(stateMutability.toLowerCase())) {
  97. throw new Error(`Methods with state mutability "${stateMutability}" must use send()`);
  98. }
  99. }
  100. options = {
  101. ...this.defaultOptions,
  102. from: this.tronWeb.defaultAddress.hex,
  103. ...options,
  104. _isConstant: true,
  105. };
  106. const parameters = args.map((value, index) => ({
  107. type: types[index],
  108. value,
  109. }));
  110. const transaction = await this.tronWeb.transactionBuilder.triggerSmartContract(this.contract.address, this.functionSelector, options, parameters, options.from ? this.tronWeb.address.toHex(options.from) : undefined);
  111. if (!utils.hasProperty(transaction, 'constant_result')) {
  112. throw new Error('Failed to execute');
  113. }
  114. const len = transaction.constant_result[0].length;
  115. if (len === 0 || len % 64 === 8) {
  116. let msg = 'The call has been reverted or has thrown an error.';
  117. if (len !== 0) {
  118. msg += ' Error message: ';
  119. let msg2 = '';
  120. const chunk = transaction.constant_result[0].substring(8);
  121. for (let i = 0; i < len - 8; i += 64) {
  122. msg2 += this.tronWeb.toUtf8(chunk.substring(i, i + 64));
  123. }
  124. msg += msg2
  125. .replace(/(\u0000|\u000b|\f)+/g, ' ')
  126. .replace(/ +/g, ' ')
  127. .replace(/\s+$/g, '');
  128. }
  129. throw new Error(msg);
  130. }
  131. let output = decodeOutput(this.abi, '0x' + transaction.constant_result[0]);
  132. if (output.length === 1 && Object.keys(output).length === 1) {
  133. output = output[0];
  134. }
  135. return output;
  136. }
  137. async _send(types, args, options = {}, privateKey = this.tronWeb.defaultPrivateKey) {
  138. if (types.length !== args.length) {
  139. throw new Error('Invalid argument count provided');
  140. }
  141. if (!this.contract.address) {
  142. throw new Error('Smart contract is missing address');
  143. }
  144. if (!this.contract.deployed) {
  145. throw new Error('Calling smart contracts requires you to load the contract first');
  146. }
  147. const { stateMutability } = this.abi;
  148. if (['pure', 'view'].includes(stateMutability.toLowerCase())) {
  149. throw new Error(`Methods with state mutability "${stateMutability}" must use call()`);
  150. }
  151. // If a function isn't payable, dont provide a callValue.
  152. if (!['payable'].includes(stateMutability.toLowerCase())) {
  153. options.callValue = 0;
  154. }
  155. options = {
  156. ...this.defaultOptions,
  157. from: this.tronWeb.defaultAddress.hex,
  158. ...options,
  159. };
  160. const parameters = args.map((value, index) => ({
  161. type: types[index],
  162. value,
  163. }));
  164. const address = privateKey ? this.tronWeb.address.fromPrivateKey(privateKey) : this.tronWeb.defaultAddress.base58;
  165. const transaction = await this.tronWeb.transactionBuilder.triggerSmartContract(this.contract.address, this.functionSelector, options, parameters, this.tronWeb.address.toHex(address));
  166. if (!transaction.result || !transaction.result.result) {
  167. throw new Error('Unknown error: ' + JSON.stringify(transaction, null, 2));
  168. }
  169. // If privateKey is false, this won't be signed here. We assume sign functionality will be replaced.
  170. const signedTransaction = await this.tronWeb.trx.sign(transaction.transaction, privateKey);
  171. if (!signedTransaction.signature) {
  172. if (!privateKey) {
  173. throw new Error('Transaction was not signed properly');
  174. }
  175. throw new Error('Invalid private key provided');
  176. }
  177. const broadcast = await this.tronWeb.trx.sendRawTransaction(signedTransaction);
  178. if (broadcast.code) {
  179. const err = {
  180. error: broadcast.code,
  181. message: broadcast.code,
  182. };
  183. if (broadcast.message)
  184. err.message = this.tronWeb.toUtf8(broadcast.message);
  185. const error = new Error(err.message);
  186. error.error = broadcast.code;
  187. throw error;
  188. }
  189. if (!options.shouldPollResponse) {
  190. return signedTransaction.txID;
  191. }
  192. const checkResult = async (index) => {
  193. if (index === (options.pollTimes || 20)) {
  194. const error = new Error('Cannot find result in solidity node');
  195. error.error = 'Cannot find result in solidity node';
  196. error.transaction = signedTransaction;
  197. throw error;
  198. }
  199. const output = await this.tronWeb.trx.getTransactionInfo(signedTransaction.txID);
  200. if (!Object.keys(output).length) {
  201. await new Promise((r) => setTimeout(r, 3000));
  202. return checkResult(index + 1);
  203. }
  204. if (output.result && output.result === 'FAILED') {
  205. const error = new Error(this.tronWeb.toUtf8(output.resMessage));
  206. error.error = this.tronWeb.toUtf8(output.resMessage);
  207. error.transaction = signedTransaction;
  208. error.output = output;
  209. throw error;
  210. }
  211. if (!utils.hasProperty(output, 'contractResult')) {
  212. const error = new Error('Failed to execute: ' + JSON.stringify(output, null, 2));
  213. error.error = 'Failed to execute: ' + JSON.stringify(output, null, 2);
  214. error.transaction = signedTransaction;
  215. error.output = output;
  216. throw error;
  217. }
  218. if (options.rawResponse) {
  219. return output;
  220. }
  221. let decoded = decodeOutput(this.abi, '0x' + output.contractResult[0]);
  222. if (decoded.length === 1 && Object.keys(decoded).length === 1) {
  223. decoded = decoded[0];
  224. }
  225. if (options.keepTxID) {
  226. return [signedTransaction.txID, decoded];
  227. }
  228. return decoded;
  229. };
  230. return checkResult(0);
  231. }
  232. }
  233. //# sourceMappingURL=method.js.map