interface.js 24 KB

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