interface.ts 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715
  1. "use strict";
  2. import { getAddress } from "@ethersproject/address";
  3. import { BigNumber, BigNumberish } from "@ethersproject/bignumber";
  4. import { arrayify, BytesLike, concat, hexDataSlice, hexlify, hexZeroPad, isHexString } from "@ethersproject/bytes";
  5. import { id } from "@ethersproject/hash";
  6. import { keccak256 } from "@ethersproject/keccak256"
  7. import { defineReadOnly, Description, getStatic } from "@ethersproject/properties";
  8. import { AbiCoder, defaultAbiCoder } from "./abi-coder";
  9. import { checkResultErrors, Result } from "./coders/abstract-coder";
  10. import { ConstructorFragment, ErrorFragment, EventFragment, FormatTypes, Fragment, FunctionFragment, JsonFragment, ParamType } from "./fragments";
  11. import { Logger } from "@ethersproject/logger";
  12. import { version } from "./_version";
  13. const logger = new Logger(version);
  14. export { checkResultErrors, Result };
  15. export class LogDescription extends Description<LogDescription> {
  16. readonly eventFragment: EventFragment;
  17. readonly name: string;
  18. readonly signature: string;
  19. readonly topic: string;
  20. readonly args: Result
  21. }
  22. export class TransactionDescription extends Description<TransactionDescription> {
  23. readonly functionFragment: FunctionFragment;
  24. readonly name: string;
  25. readonly args: Result;
  26. readonly signature: string;
  27. readonly sighash: string;
  28. readonly value: BigNumber;
  29. }
  30. export class ErrorDescription extends Description<ErrorDescription> {
  31. readonly errorFragment: ErrorFragment;
  32. readonly name: string;
  33. readonly args: Result;
  34. readonly signature: string;
  35. readonly sighash: string;
  36. }
  37. export class Indexed extends Description<Indexed> {
  38. readonly hash: string;
  39. readonly _isIndexed: boolean;
  40. static isIndexed(value: any): value is Indexed {
  41. return !!(value && value._isIndexed);
  42. }
  43. }
  44. const BuiltinErrors: Record<string, { signature: string, inputs: Array<string>, name: string, reason?: boolean }> = {
  45. "0x08c379a0": { signature: "Error(string)", name: "Error", inputs: [ "string" ], reason: true },
  46. "0x4e487b71": { signature: "Panic(uint256)", name: "Panic", inputs: [ "uint256" ] }
  47. }
  48. function wrapAccessError(property: string, error: Error): Error {
  49. const wrap = new Error(`deferred error during ABI decoding triggered accessing ${ property }`);
  50. (<any>wrap).error = error;
  51. return wrap;
  52. }
  53. /*
  54. function checkNames(fragment: Fragment, type: "input" | "output", params: Array<ParamType>): void {
  55. params.reduce((accum, param) => {
  56. if (param.name) {
  57. if (accum[param.name]) {
  58. logger.throwArgumentError(`duplicate ${ type } parameter ${ JSON.stringify(param.name) } in ${ fragment.format("full") }`, "fragment", fragment);
  59. }
  60. accum[param.name] = true;
  61. }
  62. return accum;
  63. }, <{ [ name: string ]: boolean }>{ });
  64. }
  65. */
  66. export class Interface {
  67. readonly fragments: ReadonlyArray<Fragment>;
  68. readonly errors: { [ name: string ]: ErrorFragment };
  69. readonly events: { [ name: string ]: EventFragment };
  70. readonly functions: { [ name: string ]: FunctionFragment };
  71. readonly structs: { [ name: string ]: any };
  72. readonly deploy: ConstructorFragment;
  73. readonly _abiCoder: AbiCoder;
  74. readonly _isInterface: boolean;
  75. constructor(fragments: string | ReadonlyArray<Fragment | JsonFragment | string>) {
  76. let abi: ReadonlyArray<Fragment | JsonFragment | string> = [ ];
  77. if (typeof(fragments) === "string") {
  78. abi = JSON.parse(fragments);
  79. } else {
  80. abi = fragments;
  81. }
  82. defineReadOnly(this, "fragments", abi.map((fragment) => {
  83. return Fragment.from(fragment);
  84. }).filter((fragment) => (fragment != null)));
  85. defineReadOnly(this, "_abiCoder", getStatic<() => AbiCoder>(new.target, "getAbiCoder")());
  86. defineReadOnly(this, "functions", { });
  87. defineReadOnly(this, "errors", { });
  88. defineReadOnly(this, "events", { });
  89. defineReadOnly(this, "structs", { });
  90. // Add all fragments by their signature
  91. this.fragments.forEach((fragment) => {
  92. let bucket: { [ name: string ]: Fragment } = null;
  93. switch (fragment.type) {
  94. case "constructor":
  95. if (this.deploy) {
  96. logger.warn("duplicate definition - constructor");
  97. return;
  98. }
  99. //checkNames(fragment, "input", fragment.inputs);
  100. defineReadOnly(this, "deploy", <ConstructorFragment>fragment);
  101. return;
  102. case "function":
  103. //checkNames(fragment, "input", fragment.inputs);
  104. //checkNames(fragment, "output", (<FunctionFragment>fragment).outputs);
  105. bucket = this.functions;
  106. break;
  107. case "event":
  108. //checkNames(fragment, "input", fragment.inputs);
  109. bucket = this.events;
  110. break;
  111. case "error":
  112. bucket = this.errors;
  113. break;
  114. default:
  115. return;
  116. }
  117. let signature = fragment.format();
  118. if (bucket[signature]) {
  119. logger.warn("duplicate definition - " + signature);
  120. return;
  121. }
  122. bucket[signature] = fragment;
  123. });
  124. // If we do not have a constructor add a default
  125. if (!this.deploy) {
  126. defineReadOnly(this, "deploy", ConstructorFragment.from({
  127. payable: false,
  128. type: "constructor"
  129. }));
  130. }
  131. defineReadOnly(this, "_isInterface", true);
  132. }
  133. format(format?: string): string | Array<string> {
  134. if (!format) { format = FormatTypes.full; }
  135. if (format === FormatTypes.sighash) {
  136. logger.throwArgumentError("interface does not support formatting sighash", "format", format);
  137. }
  138. const abi = this.fragments.map((fragment) => fragment.format(format));
  139. // We need to re-bundle the JSON fragments a bit
  140. if (format === FormatTypes.json) {
  141. return JSON.stringify(abi.map((j) => JSON.parse(j)));
  142. }
  143. return abi;
  144. }
  145. // Sub-classes can override these to handle other blockchains
  146. static getAbiCoder(): AbiCoder {
  147. return defaultAbiCoder;
  148. }
  149. static getAddress(address: string): string {
  150. return getAddress(address);
  151. }
  152. static getSighash(fragment: ErrorFragment | FunctionFragment): string {
  153. return hexDataSlice(id(fragment.format()), 0, 4);
  154. }
  155. static getEventTopic(eventFragment: EventFragment): string {
  156. return id(eventFragment.format());
  157. }
  158. // Find a function definition by any means necessary (unless it is ambiguous)
  159. getFunction(nameOrSignatureOrSighash: string): FunctionFragment {
  160. if (isHexString(nameOrSignatureOrSighash)) {
  161. for (const name in this.functions) {
  162. if (nameOrSignatureOrSighash === this.getSighash(name)) {
  163. return this.functions[name];
  164. }
  165. }
  166. logger.throwArgumentError("no matching function", "sighash", nameOrSignatureOrSighash);
  167. }
  168. // It is a bare name, look up the function (will return null if ambiguous)
  169. if (nameOrSignatureOrSighash.indexOf("(") === -1) {
  170. const name = nameOrSignatureOrSighash.trim();
  171. const matching = Object.keys(this.functions).filter((f) => (f.split("("/* fix:) */)[0] === name));
  172. if (matching.length === 0) {
  173. logger.throwArgumentError("no matching function", "name", name);
  174. } else if (matching.length > 1) {
  175. logger.throwArgumentError("multiple matching functions", "name", name);
  176. }
  177. return this.functions[matching[0]];
  178. }
  179. // Normalize the signature and lookup the function
  180. const result = this.functions[FunctionFragment.fromString(nameOrSignatureOrSighash).format()];
  181. if (!result) {
  182. logger.throwArgumentError("no matching function", "signature", nameOrSignatureOrSighash);
  183. }
  184. return result;
  185. }
  186. // Find an event definition by any means necessary (unless it is ambiguous)
  187. getEvent(nameOrSignatureOrTopic: string): EventFragment {
  188. if (isHexString(nameOrSignatureOrTopic)) {
  189. const topichash = nameOrSignatureOrTopic.toLowerCase();
  190. for (const name in this.events) {
  191. if (topichash === this.getEventTopic(name)) {
  192. return this.events[name];
  193. }
  194. }
  195. logger.throwArgumentError("no matching event", "topichash", topichash);
  196. }
  197. // It is a bare name, look up the function (will return null if ambiguous)
  198. if (nameOrSignatureOrTopic.indexOf("(") === -1) {
  199. const name = nameOrSignatureOrTopic.trim();
  200. const matching = Object.keys(this.events).filter((f) => (f.split("("/* fix:) */)[0] === name));
  201. if (matching.length === 0) {
  202. logger.throwArgumentError("no matching event", "name", name);
  203. } else if (matching.length > 1) {
  204. logger.throwArgumentError("multiple matching events", "name", name);
  205. }
  206. return this.events[matching[0]];
  207. }
  208. // Normalize the signature and lookup the function
  209. const result = this.events[EventFragment.fromString(nameOrSignatureOrTopic).format()];
  210. if (!result) {
  211. logger.throwArgumentError("no matching event", "signature", nameOrSignatureOrTopic);
  212. }
  213. return result;
  214. }
  215. // Find a function definition by any means necessary (unless it is ambiguous)
  216. getError(nameOrSignatureOrSighash: string): ErrorFragment {
  217. if (isHexString(nameOrSignatureOrSighash)) {
  218. const getSighash = getStatic<(f: ErrorFragment | FunctionFragment) => string>(this.constructor, "getSighash");
  219. for (const name in this.errors) {
  220. const error = this.errors[name];
  221. if (nameOrSignatureOrSighash === getSighash(error)) {
  222. return this.errors[name];
  223. }
  224. }
  225. logger.throwArgumentError("no matching error", "sighash", nameOrSignatureOrSighash);
  226. }
  227. // It is a bare name, look up the function (will return null if ambiguous)
  228. if (nameOrSignatureOrSighash.indexOf("(") === -1) {
  229. const name = nameOrSignatureOrSighash.trim();
  230. const matching = Object.keys(this.errors).filter((f) => (f.split("("/* fix:) */)[0] === name));
  231. if (matching.length === 0) {
  232. logger.throwArgumentError("no matching error", "name", name);
  233. } else if (matching.length > 1) {
  234. logger.throwArgumentError("multiple matching errors", "name", name);
  235. }
  236. return this.errors[matching[0]];
  237. }
  238. // Normalize the signature and lookup the function
  239. const result = this.errors[FunctionFragment.fromString(nameOrSignatureOrSighash).format()];
  240. if (!result) {
  241. logger.throwArgumentError("no matching error", "signature", nameOrSignatureOrSighash);
  242. }
  243. return result;
  244. }
  245. // Get the sighash (the bytes4 selector) used by Solidity to identify a function
  246. getSighash(fragment: ErrorFragment | FunctionFragment | string): string {
  247. if (typeof(fragment) === "string") {
  248. try {
  249. fragment = this.getFunction(fragment);
  250. } catch (error) {
  251. try {
  252. fragment = this.getError(<string>fragment);
  253. } catch (_) {
  254. throw error;
  255. }
  256. }
  257. }
  258. return getStatic<(f: ErrorFragment | FunctionFragment) => string>(this.constructor, "getSighash")(fragment);
  259. }
  260. // Get the topic (the bytes32 hash) used by Solidity to identify an event
  261. getEventTopic(eventFragment: EventFragment | string): string {
  262. if (typeof(eventFragment) === "string") {
  263. eventFragment = this.getEvent(eventFragment);
  264. }
  265. return getStatic<(e: EventFragment) => string>(this.constructor, "getEventTopic")(eventFragment);
  266. }
  267. _decodeParams(params: ReadonlyArray<ParamType>, data: BytesLike): Result {
  268. return this._abiCoder.decode(params, data)
  269. }
  270. _encodeParams(params: ReadonlyArray<ParamType>, values: ReadonlyArray<any>): string {
  271. return this._abiCoder.encode(params, values)
  272. }
  273. encodeDeploy(values?: ReadonlyArray<any>): string {
  274. return this._encodeParams(this.deploy.inputs, values || [ ]);
  275. }
  276. decodeErrorResult(fragment: ErrorFragment | string, data: BytesLike): Result {
  277. if (typeof(fragment) === "string") {
  278. fragment = this.getError(fragment);
  279. }
  280. const bytes = arrayify(data);
  281. if (hexlify(bytes.slice(0, 4)) !== this.getSighash(fragment)) {
  282. logger.throwArgumentError(`data signature does not match error ${ fragment.name }.`, "data", hexlify(bytes));
  283. }
  284. return this._decodeParams(fragment.inputs, bytes.slice(4));
  285. }
  286. encodeErrorResult(fragment: ErrorFragment | string, values?: ReadonlyArray<any>): string {
  287. if (typeof(fragment) === "string") {
  288. fragment = this.getError(fragment);
  289. }
  290. return hexlify(concat([
  291. this.getSighash(fragment),
  292. this._encodeParams(fragment.inputs, values || [ ])
  293. ]));
  294. }
  295. // Decode the data for a function call (e.g. tx.data)
  296. decodeFunctionData(functionFragment: FunctionFragment | string, data: BytesLike): Result {
  297. if (typeof(functionFragment) === "string") {
  298. functionFragment = this.getFunction(functionFragment);
  299. }
  300. const bytes = arrayify(data);
  301. if (hexlify(bytes.slice(0, 4)) !== this.getSighash(functionFragment)) {
  302. logger.throwArgumentError(`data signature does not match function ${ functionFragment.name }.`, "data", hexlify(bytes));
  303. }
  304. return this._decodeParams(functionFragment.inputs, bytes.slice(4));
  305. }
  306. // Encode the data for a function call (e.g. tx.data)
  307. encodeFunctionData(functionFragment: FunctionFragment | string, values?: ReadonlyArray<any>): string {
  308. if (typeof(functionFragment) === "string") {
  309. functionFragment = this.getFunction(functionFragment);
  310. }
  311. return hexlify(concat([
  312. this.getSighash(functionFragment),
  313. this._encodeParams(functionFragment.inputs, values || [ ])
  314. ]));
  315. }
  316. // Decode the result from a function call (e.g. from eth_call)
  317. decodeFunctionResult(functionFragment: FunctionFragment | string, data: BytesLike): Result {
  318. if (typeof(functionFragment) === "string") {
  319. functionFragment = this.getFunction(functionFragment);
  320. }
  321. let bytes = arrayify(data);
  322. let reason: string = null;
  323. let message = "";
  324. let errorArgs: Result = null;
  325. let errorName: string = null;
  326. let errorSignature: string = null;
  327. switch (bytes.length % this._abiCoder._getWordSize()) {
  328. case 0:
  329. try {
  330. return this._abiCoder.decode(functionFragment.outputs, bytes);
  331. } catch (error) { }
  332. break;
  333. case 4: {
  334. const selector = hexlify(bytes.slice(0, 4));
  335. const builtin = BuiltinErrors[selector];
  336. if (builtin) {
  337. errorArgs = this._abiCoder.decode(builtin.inputs, bytes.slice(4));
  338. errorName = builtin.name;
  339. errorSignature = builtin.signature;
  340. if (builtin.reason) { reason = errorArgs[0]; }
  341. if (errorName === "Error") {
  342. message = `; VM Exception while processing transaction: reverted with reason string ${ JSON.stringify(errorArgs[0]) }`;
  343. } else if (errorName === "Panic") {
  344. message = `; VM Exception while processing transaction: reverted with panic code ${ errorArgs[0] }`;
  345. }
  346. } else {
  347. try {
  348. const error = this.getError(selector);
  349. errorArgs = this._abiCoder.decode(error.inputs, bytes.slice(4));
  350. errorName = error.name;
  351. errorSignature = error.format();
  352. } catch (error) { }
  353. }
  354. break;
  355. }
  356. }
  357. return logger.throwError("call revert exception" + message, Logger.errors.CALL_EXCEPTION, {
  358. method: functionFragment.format(),
  359. data: hexlify(data), errorArgs, errorName, errorSignature, reason
  360. });
  361. }
  362. // Encode the result for a function call (e.g. for eth_call)
  363. encodeFunctionResult(functionFragment: FunctionFragment | string, values?: ReadonlyArray<any>): string {
  364. if (typeof(functionFragment) === "string") {
  365. functionFragment = this.getFunction(functionFragment);
  366. }
  367. return hexlify(this._abiCoder.encode(functionFragment.outputs, values || [ ]));
  368. }
  369. // Create the filter for the event with search criteria (e.g. for eth_filterLog)
  370. encodeFilterTopics(eventFragment: EventFragment | string, values: ReadonlyArray<any>): Array<string | Array<string>> {
  371. if (typeof(eventFragment) === "string") {
  372. eventFragment = this.getEvent(eventFragment);
  373. }
  374. if (values.length > eventFragment.inputs.length) {
  375. logger.throwError("too many arguments for " + eventFragment.format(), Logger.errors.UNEXPECTED_ARGUMENT, {
  376. argument: "values",
  377. value: values
  378. })
  379. }
  380. let topics: Array<string | Array<string>> = [];
  381. if (!eventFragment.anonymous) { topics.push(this.getEventTopic(eventFragment)); }
  382. const encodeTopic = (param: ParamType, value: any): string => {
  383. if (param.type === "string") {
  384. return id(value);
  385. } else if (param.type === "bytes") {
  386. return keccak256(hexlify(value));
  387. }
  388. if (param.type === "bool" && typeof(value) === "boolean") {
  389. value = (value ? "0x01": "0x00");
  390. }
  391. if (param.type.match(/^u?int/)) {
  392. value = BigNumber.from(value).toHexString();
  393. }
  394. // Check addresses are valid
  395. if (param.type === "address") { this._abiCoder.encode( [ "address" ], [ value ]); }
  396. return hexZeroPad(hexlify(value), 32);
  397. };
  398. values.forEach((value, index) => {
  399. let param = (<EventFragment>eventFragment).inputs[index];
  400. if (!param.indexed) {
  401. if (value != null) {
  402. logger.throwArgumentError("cannot filter non-indexed parameters; must be null", ("contract." + param.name), value);
  403. }
  404. return;
  405. }
  406. if (value == null) {
  407. topics.push(null);
  408. } else if (param.baseType === "array" || param.baseType === "tuple") {
  409. logger.throwArgumentError("filtering with tuples or arrays not supported", ("contract." + param.name), value);
  410. } else if (Array.isArray(value)) {
  411. topics.push(value.map((value) => encodeTopic(param, value)));
  412. } else {
  413. topics.push(encodeTopic(param, value));
  414. }
  415. });
  416. // Trim off trailing nulls
  417. while (topics.length && topics[topics.length - 1] === null) {
  418. topics.pop();
  419. }
  420. return topics;
  421. }
  422. encodeEventLog(eventFragment: EventFragment | string, values: ReadonlyArray<any>): { data: string, topics: Array<string> } {
  423. if (typeof(eventFragment) === "string") {
  424. eventFragment = this.getEvent(eventFragment);
  425. }
  426. const topics: Array<string> = [ ];
  427. const dataTypes: Array<ParamType> = [ ];
  428. const dataValues: Array<string> = [ ];
  429. if (!eventFragment.anonymous) {
  430. topics.push(this.getEventTopic(eventFragment));
  431. }
  432. if (values.length !== eventFragment.inputs.length) {
  433. logger.throwArgumentError("event arguments/values mismatch", "values", values);
  434. }
  435. eventFragment.inputs.forEach((param, index) => {
  436. const value = values[index];
  437. if (param.indexed) {
  438. if (param.type === "string") {
  439. topics.push(id(value))
  440. } else if (param.type === "bytes") {
  441. topics.push(keccak256(value))
  442. } else if (param.baseType === "tuple" || param.baseType === "array") {
  443. // @TODO
  444. throw new Error("not implemented");
  445. } else {
  446. topics.push(this._abiCoder.encode([ param.type] , [ value ]));
  447. }
  448. } else {
  449. dataTypes.push(param);
  450. dataValues.push(value);
  451. }
  452. });
  453. return {
  454. data: this._abiCoder.encode(dataTypes , dataValues),
  455. topics: topics
  456. };
  457. }
  458. // Decode a filter for the event and the search criteria
  459. decodeEventLog(eventFragment: EventFragment | string, data: BytesLike, topics?: ReadonlyArray<string>): Result {
  460. if (typeof(eventFragment) === "string") {
  461. eventFragment = this.getEvent(eventFragment);
  462. }
  463. if (topics != null && !eventFragment.anonymous) {
  464. let topicHash = this.getEventTopic(eventFragment);
  465. if (!isHexString(topics[0], 32) || topics[0].toLowerCase() !== topicHash) {
  466. logger.throwError("fragment/topic mismatch", Logger.errors.INVALID_ARGUMENT, { argument: "topics[0]", expected: topicHash, value: topics[0] });
  467. }
  468. topics = topics.slice(1);
  469. }
  470. let indexed: Array<ParamType> = [];
  471. let nonIndexed: Array<ParamType> = [];
  472. let dynamic: Array<boolean> = [];
  473. eventFragment.inputs.forEach((param, index) => {
  474. if (param.indexed) {
  475. if (param.type === "string" || param.type === "bytes" || param.baseType === "tuple" || param.baseType === "array") {
  476. indexed.push(ParamType.fromObject({ type: "bytes32", name: param.name }));
  477. dynamic.push(true);
  478. } else {
  479. indexed.push(param);
  480. dynamic.push(false);
  481. }
  482. } else {
  483. nonIndexed.push(param);
  484. dynamic.push(false);
  485. }
  486. });
  487. let resultIndexed = (topics != null) ? this._abiCoder.decode(indexed, concat(topics)): null;
  488. let resultNonIndexed = this._abiCoder.decode(nonIndexed, data, true);
  489. let result: (Array<any> & { [ key: string ]: any }) = [ ];
  490. let nonIndexedIndex = 0, indexedIndex = 0;
  491. eventFragment.inputs.forEach((param, index) => {
  492. if (param.indexed) {
  493. if (resultIndexed == null) {
  494. result[index] = new Indexed({ _isIndexed: true, hash: null });
  495. } else if (dynamic[index]) {
  496. result[index] = new Indexed({ _isIndexed: true, hash: resultIndexed[indexedIndex++] });
  497. } else {
  498. try {
  499. result[index] = resultIndexed[indexedIndex++];
  500. } catch (error) {
  501. result[index] = error;
  502. }
  503. }
  504. } else {
  505. try {
  506. result[index] = resultNonIndexed[nonIndexedIndex++];
  507. } catch (error) {
  508. result[index] = error;
  509. }
  510. }
  511. // Add the keyword argument if named and safe
  512. if (param.name && result[param.name] == null) {
  513. const value = result[index];
  514. // Make error named values throw on access
  515. if (value instanceof Error) {
  516. Object.defineProperty(result, param.name, {
  517. enumerable: true,
  518. get: () => { throw wrapAccessError(`property ${ JSON.stringify(param.name) }`, value); }
  519. });
  520. } else {
  521. result[param.name] = value;
  522. }
  523. }
  524. });
  525. // Make all error indexed values throw on access
  526. for (let i = 0; i < result.length; i++) {
  527. const value = result[i];
  528. if (value instanceof Error) {
  529. Object.defineProperty(result, i, {
  530. enumerable: true,
  531. get: () => { throw wrapAccessError(`index ${ i }`, value); }
  532. });
  533. }
  534. }
  535. return Object.freeze(result);
  536. }
  537. // Given a transaction, find the matching function fragment (if any) and
  538. // determine all its properties and call parameters
  539. parseTransaction(tx: { data: string, value?: BigNumberish }): TransactionDescription {
  540. let fragment = this.getFunction(tx.data.substring(0, 10).toLowerCase())
  541. if (!fragment) { return null; }
  542. return new TransactionDescription({
  543. args: this._abiCoder.decode(fragment.inputs, "0x" + tx.data.substring(10)),
  544. functionFragment: fragment,
  545. name: fragment.name,
  546. signature: fragment.format(),
  547. sighash: this.getSighash(fragment),
  548. value: BigNumber.from(tx.value || "0"),
  549. });
  550. }
  551. // @TODO
  552. //parseCallResult(data: BytesLike): ??
  553. // Given an event log, find the matching event fragment (if any) and
  554. // determine all its properties and values
  555. parseLog(log: { topics: Array<string>, data: string}): LogDescription {
  556. let fragment = this.getEvent(log.topics[0]);
  557. if (!fragment || fragment.anonymous) { return null; }
  558. // @TODO: If anonymous, and the only method, and the input count matches, should we parse?
  559. // Probably not, because just because it is the only event in the ABI does
  560. // not mean we have the full ABI; maybe just a fragment?
  561. return new LogDescription({
  562. eventFragment: fragment,
  563. name: fragment.name,
  564. signature: fragment.format(),
  565. topic: this.getEventTopic(fragment),
  566. args: this.decodeEventLog(fragment, log.data, log.topics)
  567. });
  568. }
  569. parseError(data: BytesLike): ErrorDescription {
  570. const hexData = hexlify(data);
  571. let fragment = this.getError(hexData.substring(0, 10).toLowerCase())
  572. if (!fragment) { return null; }
  573. return new ErrorDescription({
  574. args: this._abiCoder.decode(fragment.inputs, "0x" + hexData.substring(10)),
  575. errorFragment: fragment,
  576. name: fragment.name,
  577. signature: fragment.format(),
  578. sighash: this.getSighash(fragment),
  579. });
  580. }
  581. /*
  582. static from(value: Array<Fragment | string | JsonAbi> | string | Interface) {
  583. if (Interface.isInterface(value)) {
  584. return value;
  585. }
  586. if (typeof(value) === "string") {
  587. return new Interface(JSON.parse(value));
  588. }
  589. return new Interface(value);
  590. }
  591. */
  592. static isInterface(value: any): value is Interface {
  593. return !!(value && value._isInterface);
  594. }
  595. }