fragments.ts 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738
  1. /**
  2. * A fragment is a single item from an ABI, which may represent any of:
  3. *
  4. * - [Functions](FunctionFragment)
  5. * - [Events](EventFragment)
  6. * - [Constructors](ConstructorFragment)
  7. * - Custom [Errors](ErrorFragment)
  8. * - [Fallback or Receive](FallbackFragment) functions
  9. *
  10. * @_subsection api/abi/abi-coder:Fragments [about-fragments]
  11. */
  12. import { defineProperties, getBigInt, getNumber, assert, assertPrivate, assertArgument } from 'ethers';
  13. import { id } from 'ethers';
  14. /**
  15. * A Type description in a [JSON ABI format](link-solc-jsonabi).
  16. */
  17. export interface JsonFragmentType {
  18. /**
  19. * The parameter name.
  20. */
  21. readonly name?: string;
  22. /**
  23. * If the parameter is indexed.
  24. */
  25. readonly indexed?: boolean;
  26. /**
  27. * The type of the parameter.
  28. */
  29. readonly type?: string;
  30. /**
  31. * The internal Solidity type.
  32. */
  33. readonly internalType?: string;
  34. /**
  35. * The components for a tuple.
  36. */
  37. readonly components?: ReadonlyArray<JsonFragmentType>;
  38. }
  39. /**
  40. * A fragment for a method, event or error in a [JSON ABI format](link-solc-jsonabi).
  41. */
  42. export interface JsonFragment {
  43. /**
  44. * The name of the error, event, function, etc.
  45. */
  46. readonly name?: string;
  47. /**
  48. * The type of the fragment (e.g. ``event``, ``"function"``, etc.)
  49. */
  50. readonly type?: string;
  51. /**
  52. * If the event is anonymous.
  53. */
  54. readonly anonymous?: boolean;
  55. /**
  56. * If the function is payable.
  57. */
  58. readonly payable?: boolean;
  59. /**
  60. * If the function is constant.
  61. */
  62. readonly constant?: boolean;
  63. /**
  64. * The mutability state of the function.
  65. */
  66. readonly stateMutability?: string;
  67. /**
  68. * The input parameters.
  69. */
  70. readonly inputs?: ReadonlyArray<JsonFragmentType>;
  71. /**
  72. * The output parameters.
  73. */
  74. readonly outputs?: ReadonlyArray<JsonFragmentType>;
  75. /**
  76. * The gas limit to use when sending a transaction for this function.
  77. */
  78. readonly gas?: string;
  79. }
  80. /**
  81. * The format to serialize the output as.
  82. *
  83. * **``"sighash"``** - the bare formatting, used to compute the selector
  84. * or topic hash; this format cannot be reversed (as it discards ``indexed``)
  85. * so cannot by used to export an [[Interface]].
  86. *
  87. * **``"minimal"``** - Human-Readable ABI with minimal spacing and without
  88. * names, so it is compact, but will result in Result objects that cannot
  89. * be accessed by name.
  90. *
  91. * **``"full"``** - Full Human-Readable ABI, with readable spacing and names
  92. * intact; this is generally the recommended format.
  93. *
  94. * **``"json"``** - The [JSON ABI format](link-solc-jsonabi).
  95. */
  96. export type FormatType = 'sighash' | 'minimal' | 'full' | 'json';
  97. // [ "a", "b" ] => { "a": 1, "b": 1 }
  98. function setify(items: Array<string>): ReadonlySet<string> {
  99. const result: Set<string> = new Set();
  100. items.forEach((k) => result.add(k));
  101. return Object.freeze(result);
  102. }
  103. // Visibility Keywords
  104. const _kwVisib = 'constant external internal payable private public pure view';
  105. const KwVisib = setify(_kwVisib.split(' '));
  106. const _kwTypes = 'constructor error event fallback function receive struct';
  107. const KwTypes = setify(_kwTypes.split(' '));
  108. const _kwModifiers = 'calldata memory storage payable indexed';
  109. const KwModifiers = setify(_kwModifiers.split(' '));
  110. const _kwOther = 'tuple returns';
  111. // All Keywords
  112. const _keywords = [_kwTypes, _kwModifiers, _kwOther, _kwVisib].join(' ');
  113. const Keywords = setify(_keywords.split(' '));
  114. // Single character tokens
  115. const SimpleTokens: Record<string, string> = {
  116. '(': 'OPEN_PAREN',
  117. ')': 'CLOSE_PAREN',
  118. '[': 'OPEN_BRACKET',
  119. ']': 'CLOSE_BRACKET',
  120. ',': 'COMMA',
  121. '@': 'AT',
  122. };
  123. // Parser regexes to consume the next token
  124. const regexWhitespacePrefix = new RegExp('^(\\s*)');
  125. const regexNumberPrefix = new RegExp('^([0-9]+)');
  126. const regexIdPrefix = new RegExp('^([a-zA-Z$_][a-zA-Z0-9$_]*)');
  127. // Parser regexs to check validity
  128. const regexId = new RegExp('^([a-zA-Z$_][a-zA-Z0-9$_]*)$');
  129. const regexType = new RegExp('^(trcToken|address|bool|bytes([0-9]*)|string|u?int([0-9]*))$');
  130. /**
  131. * @ignore:
  132. */
  133. type Token = Readonly<{
  134. // Type of token (e.g. TYPE, KEYWORD, NUMBER, etc)
  135. type: string;
  136. // Offset into the original source code
  137. offset: number;
  138. // Actual text content of the token
  139. text: string;
  140. // The parenthesis depth
  141. depth: number;
  142. // If a parenthesis, the offset (in tokens) that balances it
  143. match: number;
  144. // For parenthesis and commas, the offset (in tokens) to the
  145. // previous/next parenthesis or comma in the list
  146. linkBack: number;
  147. linkNext: number;
  148. // If a BRACKET, the value inside
  149. value: number;
  150. }>;
  151. class TokenString {
  152. #offset: number;
  153. #tokens: ReadonlyArray<Token>;
  154. get offset(): number {
  155. return this.#offset;
  156. }
  157. get length(): number {
  158. return this.#tokens.length - this.#offset;
  159. }
  160. constructor(tokens: ReadonlyArray<Token>) {
  161. this.#offset = 0;
  162. this.#tokens = tokens.slice();
  163. }
  164. clone(): TokenString {
  165. return new TokenString(this.#tokens);
  166. }
  167. reset(): void {
  168. this.#offset = 0;
  169. }
  170. #subTokenString(from = 0, to = 0): TokenString {
  171. return new TokenString(
  172. this.#tokens.slice(from, to).map((t) => {
  173. return Object.freeze(
  174. Object.assign({}, t, {
  175. match: t.match - from,
  176. linkBack: t.linkBack - from,
  177. linkNext: t.linkNext - from,
  178. })
  179. );
  180. })
  181. );
  182. }
  183. // Pops and returns the value of the next token, if it is a keyword in allowed; throws if out of tokens
  184. popKeyword(allowed: ReadonlySet<string>): string {
  185. const top = this.peek();
  186. if (top.type !== 'KEYWORD' || !allowed.has(top.text)) {
  187. throw new Error(`expected keyword ${top.text}`);
  188. }
  189. return this.pop().text;
  190. }
  191. // Pops and returns the value of the next token if it is `type`; throws if out of tokens
  192. popType(type: string): string {
  193. if (this.peek().type !== type) {
  194. throw new Error(`expected ${type}; got ${JSON.stringify(this.peek())}`);
  195. }
  196. return this.pop().text;
  197. }
  198. // Pops and returns a "(" TOKENS ")"
  199. popParen(): TokenString {
  200. const top = this.peek();
  201. if (top.type !== 'OPEN_PAREN') {
  202. throw new Error('bad start');
  203. }
  204. const result = this.#subTokenString(this.#offset + 1, top.match + 1);
  205. this.#offset = top.match + 1;
  206. return result;
  207. }
  208. // Pops and returns the items within "(" ITEM1 "," ITEM2 "," ... ")"
  209. popParams(): Array<TokenString> {
  210. const top = this.peek();
  211. if (top.type !== 'OPEN_PAREN') {
  212. throw new Error('bad start');
  213. }
  214. const result: Array<TokenString> = [];
  215. while (this.#offset < top.match - 1) {
  216. const link = this.peek().linkNext;
  217. result.push(this.#subTokenString(this.#offset + 1, link));
  218. this.#offset = link;
  219. }
  220. this.#offset = top.match + 1;
  221. return result;
  222. }
  223. // Returns the top Token, throwing if out of tokens
  224. peek(): Token {
  225. if (this.#offset >= this.#tokens.length) {
  226. throw new Error('out-of-bounds');
  227. }
  228. return this.#tokens[this.#offset];
  229. }
  230. // Returns the next value, if it is a keyword in `allowed`
  231. peekKeyword(allowed: ReadonlySet<string>): null | string {
  232. const top = this.peekType('KEYWORD');
  233. return top != null && allowed.has(top) ? top : null;
  234. }
  235. // Returns the value of the next token if it is `type`
  236. peekType(type: string): null | string {
  237. if (this.length === 0) {
  238. return null;
  239. }
  240. const top = this.peek();
  241. return top.type === type ? top.text : null;
  242. }
  243. // Returns the next token; throws if out of tokens
  244. pop(): Token {
  245. const result = this.peek();
  246. this.#offset++;
  247. return result;
  248. }
  249. toString(): string {
  250. const tokens: Array<string> = [];
  251. for (let i = this.#offset; i < this.#tokens.length; i++) {
  252. const token = this.#tokens[i];
  253. tokens.push(`${token.type}:${token.text}`);
  254. }
  255. return `<TokenString ${tokens.join(' ')}>`;
  256. }
  257. }
  258. type Writeable<T> = { -readonly [P in keyof T]: T[P] };
  259. function lex(text: string): TokenString {
  260. const tokens: Array<Token> = [];
  261. const throwError = (message: string) => {
  262. const token = offset < text.length ? JSON.stringify(text[offset]) : '$EOI';
  263. throw new Error(`invalid token ${token} at ${offset}: ${message}`);
  264. };
  265. const brackets: Array<number> = [];
  266. const commas: Array<number> = [];
  267. let offset = 0;
  268. while (offset < text.length) {
  269. // Strip off any leading whitespace
  270. let cur = text.substring(offset);
  271. let match = cur.match(regexWhitespacePrefix);
  272. if (match) {
  273. offset += match[1].length;
  274. cur = text.substring(offset);
  275. }
  276. const token = {
  277. depth: brackets.length,
  278. linkBack: -1,
  279. linkNext: -1,
  280. match: -1,
  281. type: '',
  282. text: '',
  283. offset,
  284. value: -1,
  285. };
  286. tokens.push(token);
  287. const type = SimpleTokens[cur[0]] || '';
  288. if (type) {
  289. token.type = type;
  290. token.text = cur[0];
  291. offset++;
  292. if (type === 'OPEN_PAREN') {
  293. brackets.push(tokens.length - 1);
  294. commas.push(tokens.length - 1);
  295. } else if (type == 'CLOSE_PAREN') {
  296. if (brackets.length === 0) {
  297. throwError('no matching open bracket');
  298. }
  299. token.match = brackets.pop() as number;
  300. (<Writeable<Token>>tokens[token.match]).match = tokens.length - 1;
  301. token.depth--;
  302. token.linkBack = commas.pop() as number;
  303. (<Writeable<Token>>tokens[token.linkBack]).linkNext = tokens.length - 1;
  304. } else if (type === 'COMMA') {
  305. token.linkBack = commas.pop() as number;
  306. (<Writeable<Token>>tokens[token.linkBack]).linkNext = tokens.length - 1;
  307. commas.push(tokens.length - 1);
  308. } else if (type === 'OPEN_BRACKET') {
  309. token.type = 'BRACKET';
  310. } else if (type === 'CLOSE_BRACKET') {
  311. // Remove the CLOSE_BRACKET
  312. let suffix = (tokens.pop() as Token).text;
  313. if (tokens.length > 0 && tokens[tokens.length - 1].type === 'NUMBER') {
  314. const value = (tokens.pop() as Token).text;
  315. suffix = value + suffix;
  316. (<Writeable<Token>>tokens[tokens.length - 1]).value = getNumber(value);
  317. }
  318. if (tokens.length === 0 || tokens[tokens.length - 1].type !== 'BRACKET') {
  319. throw new Error('missing opening bracket');
  320. }
  321. (<Writeable<Token>>tokens[tokens.length - 1]).text += suffix;
  322. }
  323. continue;
  324. }
  325. match = cur.match(regexIdPrefix);
  326. if (match) {
  327. token.text = match[1];
  328. offset += token.text.length;
  329. if (Keywords.has(token.text)) {
  330. token.type = 'KEYWORD';
  331. continue;
  332. }
  333. if (token.text.match(regexType)) {
  334. token.type = 'TYPE';
  335. continue;
  336. }
  337. token.type = 'ID';
  338. continue;
  339. }
  340. match = cur.match(regexNumberPrefix);
  341. if (match) {
  342. token.text = match[1];
  343. token.type = 'NUMBER';
  344. offset += token.text.length;
  345. continue;
  346. }
  347. throw new Error(`unexpected token ${JSON.stringify(cur[0])} at position ${offset}`);
  348. }
  349. return new TokenString(tokens.map((t) => Object.freeze(t)));
  350. }
  351. // Check only one of `allowed` is in `set`
  352. function allowSingle(set: ReadonlySet<string>, allowed: ReadonlySet<string>): void {
  353. const included: Array<string> = [];
  354. for (const key in allowed.keys()) {
  355. if (set.has(key)) {
  356. included.push(key);
  357. }
  358. }
  359. if (included.length > 1) {
  360. throw new Error(`conflicting types: ${included.join(', ')}`);
  361. }
  362. }
  363. // Functions to process a Solidity Signature TokenString from left-to-right for...
  364. // ...the name with an optional type, returning the name
  365. function consumeName(type: string, tokens: TokenString): string {
  366. if (tokens.peekKeyword(KwTypes)) {
  367. const keyword = tokens.pop().text;
  368. if (keyword !== type) {
  369. throw new Error(`expected ${type}, got ${keyword}`);
  370. }
  371. }
  372. return tokens.popType('ID');
  373. }
  374. // ...all keywords matching allowed, returning the keywords
  375. function consumeKeywords(tokens: TokenString, allowed?: ReadonlySet<string>): ReadonlySet<string> {
  376. const keywords: Set<string> = new Set();
  377. // eslint-disable-next-line no-constant-condition
  378. while (true) {
  379. const keyword = tokens.peekType('KEYWORD');
  380. if (keyword == null || (allowed && !allowed.has(keyword))) {
  381. break;
  382. }
  383. tokens.pop();
  384. if (keywords.has(keyword)) {
  385. throw new Error(`duplicate keywords: ${JSON.stringify(keyword)}`);
  386. }
  387. keywords.add(keyword);
  388. }
  389. return Object.freeze(keywords);
  390. }
  391. // ...all visibility keywords, returning the coalesced mutability
  392. function consumeMutability(tokens: TokenString): 'payable' | 'nonpayable' | 'view' | 'pure' {
  393. const modifiers = consumeKeywords(tokens, KwVisib);
  394. // Detect conflicting modifiers
  395. allowSingle(modifiers, setify('constant payable nonpayable'.split(' ')));
  396. allowSingle(modifiers, setify('pure view payable nonpayable'.split(' ')));
  397. // Process mutability states
  398. if (modifiers.has('view')) {
  399. return 'view';
  400. }
  401. if (modifiers.has('pure')) {
  402. return 'pure';
  403. }
  404. if (modifiers.has('payable')) {
  405. return 'payable';
  406. }
  407. if (modifiers.has('nonpayable')) {
  408. return 'nonpayable';
  409. }
  410. // Process legacy `constant` last
  411. if (modifiers.has('constant')) {
  412. return 'view';
  413. }
  414. return 'nonpayable';
  415. }
  416. // ...a parameter list, returning the ParamType list
  417. function consumeParams(tokens: TokenString, allowIndexed?: boolean): Array<ParamType> {
  418. return tokens.popParams().map((t) => ParamType.from(t, allowIndexed));
  419. }
  420. // ...a gas limit, returning a BigNumber or null if none
  421. function consumeGas(tokens: TokenString): null | bigint {
  422. if (tokens.peekType('AT')) {
  423. tokens.pop();
  424. if (tokens.peekType('NUMBER')) {
  425. return getBigInt(tokens.pop().text);
  426. }
  427. throw new Error('invalid gas');
  428. }
  429. return null;
  430. }
  431. function consumeEoi(tokens: TokenString): void {
  432. if (tokens.length) {
  433. throw new Error(`unexpected tokens: ${tokens.toString()}`);
  434. }
  435. }
  436. const regexArrayType = new RegExp(/^(.*)\[([0-9]*)\]$/);
  437. function verifyBasicType(type: string): string {
  438. const match = type.match(regexType);
  439. assertArgument(match, 'invalid type', 'type', type);
  440. if (type === 'uint') {
  441. return 'uint256';
  442. }
  443. if (type === 'int') {
  444. return 'int256';
  445. }
  446. if (match[2]) {
  447. // bytesXX
  448. const length = parseInt(match[2]);
  449. assertArgument(length !== 0 && length <= 32, 'invalid bytes length', 'type', type);
  450. } else if (match[3]) {
  451. // intXX or uintXX
  452. const size = parseInt(match[3] as string);
  453. assertArgument(size !== 0 && size <= 256 && size % 8 === 0, 'invalid numeric width', 'type', type);
  454. }
  455. return type;
  456. }
  457. // Make the Fragment constructors effectively private
  458. const _guard = {};
  459. /**
  460. * When [walking](ParamType-walk) a [[ParamType]], this is called
  461. * on each component.
  462. */
  463. export type ParamTypeWalkFunc = (type: string, value: any) => any;
  464. /**
  465. * When [walking asynchronously](ParamType-walkAsync) a [[ParamType]],
  466. * this is called on each component.
  467. */
  468. export type ParamTypeWalkAsyncFunc = (type: string, value: any) => any | Promise<any>;
  469. const internal = Symbol.for('_ethers_internal');
  470. const ParamTypeInternal = '_ParamTypeInternal';
  471. const ErrorFragmentInternal = '_ErrorInternal';
  472. const EventFragmentInternal = '_EventInternal';
  473. const ConstructorFragmentInternal = '_ConstructorInternal';
  474. const FallbackFragmentInternal = '_FallbackInternal';
  475. const FunctionFragmentInternal = '_FunctionInternal';
  476. const StructFragmentInternal = '_StructInternal';
  477. /**
  478. * Each input and output of a [[Fragment]] is an Array of **ParamType**.
  479. */
  480. export class ParamType {
  481. /**
  482. * The local name of the parameter (or ``""`` if unbound)
  483. */
  484. readonly name!: string;
  485. /**
  486. * The fully qualified type (e.g. ``"address"``, ``"tuple(address)"``,
  487. * ``"uint256[3][]"``)
  488. */
  489. readonly type!: string;
  490. /**
  491. * The base type (e.g. ``"address"``, ``"tuple"``, ``"array"``)
  492. */
  493. readonly baseType!: string;
  494. /**
  495. * True if the parameters is indexed.
  496. *
  497. * For non-indexable types this is ``null``.
  498. */
  499. readonly indexed!: null | boolean;
  500. /**
  501. * The components for the tuple.
  502. *
  503. * For non-tuple types this is ``null``.
  504. */
  505. readonly components!: null | ReadonlyArray<ParamType>;
  506. /**
  507. * The array length, or ``-1`` for dynamic-lengthed arrays.
  508. *
  509. * For non-array types this is ``null``.
  510. */
  511. readonly arrayLength!: null | number;
  512. /**
  513. * The type of each child in the array.
  514. *
  515. * For non-array types this is ``null``.
  516. */
  517. readonly arrayChildren!: null | ParamType;
  518. /**
  519. * @private
  520. */
  521. constructor(
  522. guard: any,
  523. name: string,
  524. type: string,
  525. baseType: string,
  526. indexed: null | boolean,
  527. components: null | ReadonlyArray<ParamType>,
  528. arrayLength: null | number,
  529. arrayChildren: null | ParamType
  530. ) {
  531. assertPrivate(guard, _guard, 'ParamType');
  532. Object.defineProperty(this, internal, { value: ParamTypeInternal });
  533. if (components) {
  534. components = Object.freeze(components.slice());
  535. }
  536. if (baseType === 'array') {
  537. if (arrayLength == null || arrayChildren == null) {
  538. throw new Error('');
  539. }
  540. } else if (arrayLength != null || arrayChildren != null) {
  541. throw new Error('');
  542. }
  543. if (baseType === 'tuple') {
  544. if (components == null) {
  545. throw new Error('');
  546. }
  547. } else if (components != null) {
  548. throw new Error('');
  549. }
  550. defineProperties<ParamType>(this, {
  551. name,
  552. type,
  553. baseType,
  554. indexed,
  555. components,
  556. arrayLength,
  557. arrayChildren,
  558. });
  559. }
  560. /**
  561. * Return a string representation of this type.
  562. *
  563. * For example,
  564. *
  565. * ``sighash" => "(uint256,address)"``
  566. *
  567. * ``"minimal" => "tuple(uint256,address) indexed"``
  568. *
  569. * ``"full" => "tuple(uint256 foo, address bar) indexed baz"``
  570. */
  571. format(format?: FormatType): string {
  572. if (format == null) {
  573. format = 'sighash';
  574. }
  575. if (format === 'json') {
  576. const result: any = {
  577. type: this.baseType === 'tuple' ? 'tuple' : this.type,
  578. name: this.name || undefined,
  579. };
  580. if (typeof this.indexed === 'boolean') {
  581. result.indexed = this.indexed;
  582. }
  583. if (this.isTuple()) {
  584. result.components = this.components.map((c) => JSON.parse(c.format(format)));
  585. }
  586. return JSON.stringify(result);
  587. }
  588. let result = '';
  589. // Array
  590. if (this.isArray()) {
  591. result += this.arrayChildren.format(format);
  592. result += `[${this.arrayLength < 0 ? '' : String(this.arrayLength)}]`;
  593. } else {
  594. if (this.isTuple()) {
  595. if (format !== 'sighash') {
  596. result += this.type;
  597. }
  598. result += '(' + this.components.map((comp) => comp.format(format)).join(format === 'full' ? ', ' : ',') + ')';
  599. } else {
  600. result += this.type;
  601. }
  602. }
  603. if (format !== 'sighash') {
  604. if (this.indexed === true) {
  605. result += ' indexed';
  606. }
  607. if (format === 'full' && this.name) {
  608. result += ' ' + this.name;
  609. }
  610. }
  611. return result;
  612. }
  613. /**
  614. * Returns true if %%this%% is an Array type.
  615. *
  616. * This provides a type gaurd ensuring that [[arrayChildren]]
  617. * and [[arrayLength]] are non-null.
  618. */
  619. isArray(): this is ParamType & {
  620. arrayChildren: ParamType;
  621. arrayLength: number;
  622. } {
  623. return this.baseType === 'array';
  624. }
  625. /**
  626. * Returns true if %%this%% is a Tuple type.
  627. *
  628. * This provides a type gaurd ensuring that [[components]]
  629. * is non-null.
  630. */
  631. isTuple(): this is ParamType & { components: ReadonlyArray<ParamType> } {
  632. return this.baseType === 'tuple';
  633. }
  634. /**
  635. * Returns true if %%this%% is an Indexable type.
  636. *
  637. * This provides a type gaurd ensuring that [[indexed]]
  638. * is non-null.
  639. */
  640. isIndexable(): this is ParamType & { indexed: boolean } {
  641. return this.indexed != null;
  642. }
  643. /**
  644. * Walks the **ParamType** with %%value%%, calling %%process%%
  645. * on each type, destructing the %%value%% recursively.
  646. */
  647. walk(value: any, process: ParamTypeWalkFunc): any {
  648. if (this.isArray()) {
  649. if (!Array.isArray(value)) {
  650. throw new Error('invalid array value');
  651. }
  652. if (this.arrayLength !== -1 && value.length !== this.arrayLength) {
  653. throw new Error('array is wrong length');
  654. }
  655. return value.map((v) => this.arrayChildren?.walk(v, process));
  656. }
  657. if (this.isTuple()) {
  658. if (!Array.isArray(value)) {
  659. throw new Error('invalid tuple value');
  660. }
  661. if (value.length !== this.components.length) {
  662. throw new Error('array is wrong length');
  663. }
  664. return value.map((v, i) => this.components?.[i].walk(v, process));
  665. }
  666. return process(this.type, value);
  667. }
  668. #walkAsync(
  669. promises: Array<Promise<void>>,
  670. value: any,
  671. process: ParamTypeWalkAsyncFunc,
  672. setValue: (value: any) => void
  673. ): void {
  674. if (this.isArray()) {
  675. if (!Array.isArray(value)) {
  676. throw new Error('invalid array value');
  677. }
  678. if (this.arrayLength !== -1 && value.length !== this.arrayLength) {
  679. throw new Error('array is wrong length');
  680. }
  681. const childType = this.arrayChildren;
  682. const result = value.slice();
  683. result.forEach((value, index) => {
  684. childType.#walkAsync(promises, value, process, (value: any) => {
  685. result[index] = value;
  686. });
  687. });
  688. setValue(result);
  689. return;
  690. }
  691. if (this.isTuple()) {
  692. const components = this.components;
  693. // Convert the object into an array
  694. let result: Array<any>;
  695. if (Array.isArray(value)) {
  696. result = value.slice();
  697. } else {
  698. if (value == null || typeof value !== 'object') {
  699. throw new Error('invalid tuple value');
  700. }
  701. result = components.map((param) => {
  702. if (!param.name) {
  703. throw new Error('cannot use object value with unnamed components');
  704. }
  705. if (!(param.name in value)) {
  706. throw new Error(`missing value for component ${param.name}`);
  707. }
  708. return value[param.name];
  709. });
  710. }
  711. if (result.length !== this.components.length) {
  712. throw new Error('array is wrong length');
  713. }
  714. result.forEach((value, index) => {
  715. components[index].#walkAsync(promises, value, process, (value: any) => {
  716. result[index] = value;
  717. });
  718. });
  719. setValue(result);
  720. return;
  721. }
  722. const result = process(this.type, value);
  723. if (result.then) {
  724. promises.push(
  725. (async function () {
  726. setValue(await result);
  727. })()
  728. );
  729. } else {
  730. setValue(result);
  731. }
  732. }
  733. /**
  734. * Walks the **ParamType** with %%value%%, asynchronously calling
  735. * %%process%% on each type, destructing the %%value%% recursively.
  736. *
  737. * This can be used to resolve ENS naes by walking and resolving each
  738. * ``"address"`` type.
  739. */
  740. async walkAsync(value: any, process: ParamTypeWalkAsyncFunc): Promise<any> {
  741. const promises: Array<Promise<void>> = [];
  742. const result: [any] = [value];
  743. this.#walkAsync(promises, value, process, (value: any) => {
  744. result[0] = value;
  745. });
  746. if (promises.length) {
  747. await Promise.all(promises);
  748. }
  749. return result[0];
  750. }
  751. /**
  752. * Creates a new **ParamType** for %%obj%%.
  753. *
  754. * If %%allowIndexed%% then the ``indexed`` keyword is permitted,
  755. * otherwise the ``indexed`` keyword will throw an error.
  756. */
  757. static from(obj: any, allowIndexed?: boolean): ParamType {
  758. if (ParamType.isParamType(obj)) {
  759. return obj;
  760. }
  761. if (typeof obj === 'string') {
  762. return ParamType.from(lex(obj), allowIndexed);
  763. } else if (obj instanceof TokenString) {
  764. let type = '',
  765. baseType = '';
  766. let comps: null | Array<ParamType> = null;
  767. if (consumeKeywords(obj, setify(['tuple'])).has('tuple') || obj.peekType('OPEN_PAREN')) {
  768. // Tuple
  769. baseType = 'tuple';
  770. comps = obj.popParams().map((t) => ParamType.from(t));
  771. type = `tuple(${comps.map((c) => c.format()).join(',')})`;
  772. } else {
  773. // Normal
  774. type = verifyBasicType(obj.popType('TYPE'));
  775. baseType = type;
  776. }
  777. // Check for Array
  778. let arrayChildren: null | ParamType = null;
  779. let arrayLength: null | number = null;
  780. while (obj.length && obj.peekType('BRACKET')) {
  781. const bracket = obj.pop(); //arrays[i];
  782. arrayChildren = new ParamType(_guard, '', type, baseType, null, comps, arrayLength, arrayChildren);
  783. arrayLength = bracket.value;
  784. type += bracket.text;
  785. baseType = 'array';
  786. comps = null;
  787. }
  788. let indexed: boolean | null = null;
  789. const keywords = consumeKeywords(obj, KwModifiers);
  790. if (keywords.has('indexed')) {
  791. if (!allowIndexed) {
  792. throw new Error('');
  793. }
  794. indexed = true;
  795. }
  796. const name = obj.peekType('ID') ? obj.pop().text : '';
  797. if (obj.length) {
  798. throw new Error('leftover tokens');
  799. }
  800. return new ParamType(_guard, name, type, baseType, indexed, comps, arrayLength, arrayChildren);
  801. }
  802. const name = obj.name;
  803. assertArgument(!name || (typeof name === 'string' && name.match(regexId)), 'invalid name', 'obj.name', name);
  804. let indexed = obj.indexed;
  805. if (indexed != null) {
  806. assertArgument(allowIndexed, 'parameter cannot be indexed', 'obj.indexed', obj.indexed);
  807. indexed = !!indexed;
  808. }
  809. let type = obj.type;
  810. const arrayMatch = type.match(regexArrayType);
  811. if (arrayMatch) {
  812. const arrayLength = parseInt(arrayMatch[2] || '-1');
  813. const arrayChildren = ParamType.from({
  814. type: arrayMatch[1],
  815. components: obj.components,
  816. });
  817. return new ParamType(_guard, name || '', type, 'array', indexed, null, arrayLength, arrayChildren);
  818. }
  819. if (type === 'tuple' || type.startsWith('tuple(' /* fix: ) */) || type.startsWith('(' /* fix: ) */)) {
  820. const comps = obj.components != null ? obj.components.map((c: any) => ParamType.from(c)) : null;
  821. const tuple = new ParamType(_guard, name || '', type, 'tuple', indexed, comps, null, null);
  822. // @TODO: use lexer to validate and normalize type
  823. return tuple;
  824. }
  825. type = verifyBasicType(obj.type);
  826. return new ParamType(_guard, name || '', type, type, indexed, null, null, null);
  827. }
  828. /**
  829. * Returns true if %%value%% is a **ParamType**.
  830. */
  831. static isParamType(value: any): value is ParamType {
  832. return value && value[internal] === ParamTypeInternal;
  833. }
  834. }
  835. /**
  836. * The type of a [[Fragment]].
  837. */
  838. export type FragmentType = 'constructor' | 'error' | 'event' | 'fallback' | 'function' | 'struct';
  839. /**
  840. * An abstract class to represent An individual fragment from a parse ABI.
  841. */
  842. export abstract class Fragment {
  843. /**
  844. * The type of the fragment.
  845. */
  846. readonly type!: FragmentType;
  847. /**
  848. * The inputs for the fragment.
  849. */
  850. readonly inputs!: ReadonlyArray<ParamType>;
  851. /**
  852. * @private
  853. */
  854. constructor(guard: any, type: FragmentType, inputs: ReadonlyArray<ParamType>) {
  855. assertPrivate(guard, _guard, 'Fragment');
  856. inputs = Object.freeze(inputs.slice());
  857. defineProperties<Fragment>(this, { type, inputs });
  858. }
  859. /**
  860. * Returns a string representation of this fragment as %%format%%.
  861. */
  862. abstract format(format?: FormatType): string;
  863. /**
  864. * Creates a new **Fragment** for %%obj%%, wich can be any supported
  865. * ABI frgament type.
  866. */
  867. static from(obj: any): Fragment {
  868. if (typeof obj === 'string') {
  869. // Try parsing JSON...
  870. try {
  871. Fragment.from(JSON.parse(obj));
  872. } catch (e) {
  873. //
  874. }
  875. // ...otherwise, use the human-readable lexer
  876. return Fragment.from(lex(obj));
  877. }
  878. if (obj instanceof TokenString) {
  879. // Human-readable ABI (already lexed)
  880. const type = obj.peekKeyword(KwTypes);
  881. switch (type) {
  882. case 'constructor':
  883. return ConstructorFragment.from(obj);
  884. case 'error':
  885. return ErrorFragment.from(obj);
  886. case 'event':
  887. return EventFragment.from(obj);
  888. case 'fallback':
  889. case 'receive':
  890. return FallbackFragment.from(obj);
  891. case 'function':
  892. return FunctionFragment.from(obj);
  893. case 'struct':
  894. return StructFragment.from(obj);
  895. }
  896. } else if (typeof obj === 'object') {
  897. // JSON ABI
  898. switch (obj.type) {
  899. case 'constructor':
  900. return ConstructorFragment.from(obj);
  901. case 'error':
  902. return ErrorFragment.from(obj);
  903. case 'event':
  904. return EventFragment.from(obj);
  905. case 'fallback':
  906. case 'receive':
  907. return FallbackFragment.from(obj);
  908. case 'function':
  909. return FunctionFragment.from(obj);
  910. case 'struct':
  911. return StructFragment.from(obj);
  912. }
  913. assert(false, `unsupported type: ${obj.type}`, 'UNSUPPORTED_OPERATION', {
  914. operation: 'Fragment.from',
  915. });
  916. }
  917. assertArgument(false, 'unsupported frgament object', 'obj', obj);
  918. }
  919. /**
  920. * Returns true if %%value%% is a [[ConstructorFragment]].
  921. */
  922. static isConstructor(value: any): value is ConstructorFragment {
  923. return ConstructorFragment.isFragment(value);
  924. }
  925. /**
  926. * Returns true if %%value%% is an [[ErrorFragment]].
  927. */
  928. static isError(value: any): value is ErrorFragment {
  929. return ErrorFragment.isFragment(value);
  930. }
  931. /**
  932. * Returns true if %%value%% is an [[EventFragment]].
  933. */
  934. static isEvent(value: any): value is EventFragment {
  935. return EventFragment.isFragment(value);
  936. }
  937. /**
  938. * Returns true if %%value%% is a [[FunctionFragment]].
  939. */
  940. static isFunction(value: any): value is FunctionFragment {
  941. return FunctionFragment.isFragment(value);
  942. }
  943. /**
  944. * Returns true if %%value%% is a [[StructFragment]].
  945. */
  946. static isStruct(value: any): value is StructFragment {
  947. return StructFragment.isFragment(value);
  948. }
  949. }
  950. /**
  951. * An abstract class to represent An individual fragment
  952. * which has a name from a parse ABI.
  953. */
  954. export abstract class NamedFragment extends Fragment {
  955. /**
  956. * The name of the fragment.
  957. */
  958. readonly name!: string;
  959. /**
  960. * @private
  961. */
  962. constructor(guard: any, type: FragmentType, name: string, inputs: ReadonlyArray<ParamType>) {
  963. super(guard, type, inputs);
  964. assertArgument(typeof name === 'string' && name.match(regexId), 'invalid identifier', 'name', name);
  965. inputs = Object.freeze(inputs.slice());
  966. defineProperties<NamedFragment>(this, { name });
  967. }
  968. }
  969. function joinParams(format: FormatType, params: ReadonlyArray<ParamType>): string {
  970. return '(' + params.map((p) => p.format(format)).join(format === 'full' ? ', ' : ',') + ')';
  971. }
  972. /**
  973. * A Fragment which represents a //Custom Error//.
  974. */
  975. export class ErrorFragment extends NamedFragment {
  976. /**
  977. * @private
  978. */
  979. constructor(guard: any, name: string, inputs: ReadonlyArray<ParamType>) {
  980. super(guard, 'error', name, inputs);
  981. Object.defineProperty(this, internal, { value: ErrorFragmentInternal });
  982. }
  983. /**
  984. * The Custom Error selector.
  985. */
  986. get selector(): string {
  987. return id(this.format('sighash')).substring(0, 10);
  988. }
  989. /**
  990. * Returns a string representation of this fragment as %%format%%.
  991. */
  992. format(format?: FormatType): string {
  993. if (format == null) {
  994. format = 'sighash';
  995. }
  996. if (format === 'json') {
  997. return JSON.stringify({
  998. type: 'error',
  999. name: this.name,
  1000. inputs: this.inputs.map((input) => JSON.parse(input.format(format))),
  1001. });
  1002. }
  1003. const result: string[] = [];
  1004. if (format !== 'sighash') {
  1005. result.push('error');
  1006. }
  1007. result.push(this.name + joinParams(format, this.inputs));
  1008. return result.join(' ');
  1009. }
  1010. /**
  1011. * Returns a new **ErrorFragment** for %%obj%%.
  1012. */
  1013. static from(obj: any): ErrorFragment {
  1014. if (ErrorFragment.isFragment(obj)) {
  1015. return obj;
  1016. }
  1017. if (typeof obj === 'string') {
  1018. return ErrorFragment.from(lex(obj));
  1019. } else if (obj instanceof TokenString) {
  1020. const name = consumeName('error', obj);
  1021. const inputs = consumeParams(obj);
  1022. consumeEoi(obj);
  1023. return new ErrorFragment(_guard, name, inputs);
  1024. }
  1025. return new ErrorFragment(_guard, obj.name, obj.inputs ? obj.inputs.map(ParamType.from) : []);
  1026. }
  1027. /**
  1028. * Returns ``true`` and provides a type guard if %%value%% is an
  1029. * **ErrorFragment**.
  1030. */
  1031. static isFragment(value: any): value is ErrorFragment {
  1032. return value && value[internal] === ErrorFragmentInternal;
  1033. }
  1034. }
  1035. /**
  1036. * A Fragment which represents an Event.
  1037. */
  1038. export class EventFragment extends NamedFragment {
  1039. /**
  1040. * Whether this event is anonymous.
  1041. */
  1042. readonly anonymous!: boolean;
  1043. /**
  1044. * @private
  1045. */
  1046. constructor(guard: any, name: string, inputs: ReadonlyArray<ParamType>, anonymous: boolean) {
  1047. super(guard, 'event', name, inputs);
  1048. Object.defineProperty(this, internal, { value: EventFragmentInternal });
  1049. defineProperties<EventFragment>(this, { anonymous });
  1050. }
  1051. /**
  1052. * The Event topic hash.
  1053. */
  1054. get topicHash(): string {
  1055. return id(this.format('sighash'));
  1056. }
  1057. /**
  1058. * Returns a string representation of this event as %%format%%.
  1059. */
  1060. format(format?: FormatType): string {
  1061. if (format == null) {
  1062. format = 'sighash';
  1063. }
  1064. if (format === 'json') {
  1065. return JSON.stringify({
  1066. type: 'event',
  1067. anonymous: this.anonymous,
  1068. name: this.name,
  1069. inputs: this.inputs.map((i) => JSON.parse(i.format(format))),
  1070. });
  1071. }
  1072. const result: string[] = [];
  1073. if (format !== 'sighash') {
  1074. result.push('event');
  1075. }
  1076. result.push(this.name + joinParams(format, this.inputs));
  1077. if (format !== 'sighash' && this.anonymous) {
  1078. result.push('anonymous');
  1079. }
  1080. return result.join(' ');
  1081. }
  1082. /**
  1083. * Return the topic hash for an event with %%name%% and %%params%%.
  1084. */
  1085. static getTopicHash(name: string, params?: Array<any>): string {
  1086. params = (params || []).map((p) => ParamType.from(p));
  1087. const fragment = new EventFragment(_guard, name, params, false);
  1088. return fragment.topicHash;
  1089. }
  1090. /**
  1091. * Returns a new **EventFragment** for %%obj%%.
  1092. */
  1093. static from(obj: any): EventFragment {
  1094. if (EventFragment.isFragment(obj)) {
  1095. return obj;
  1096. }
  1097. if (typeof obj === 'string') {
  1098. return EventFragment.from(lex(obj));
  1099. } else if (obj instanceof TokenString) {
  1100. const name = consumeName('event', obj);
  1101. const inputs = consumeParams(obj, true);
  1102. const anonymous = !!consumeKeywords(obj, setify(['anonymous'])).has('anonymous');
  1103. consumeEoi(obj);
  1104. return new EventFragment(_guard, name, inputs, anonymous);
  1105. }
  1106. return new EventFragment(
  1107. _guard,
  1108. obj.name,
  1109. obj.inputs ? obj.inputs.map((p: any) => ParamType.from(p, true)) : [],
  1110. !!obj.anonymous
  1111. );
  1112. }
  1113. /**
  1114. * Returns ``true`` and provides a type guard if %%value%% is an
  1115. * **EventFragment**.
  1116. */
  1117. static isFragment(value: any): value is EventFragment {
  1118. return value && value[internal] === EventFragmentInternal;
  1119. }
  1120. }
  1121. /**
  1122. * A Fragment which represents a constructor.
  1123. */
  1124. export class ConstructorFragment extends Fragment {
  1125. /**
  1126. * Whether the constructor can receive an endowment.
  1127. */
  1128. readonly payable!: boolean;
  1129. /**
  1130. * The recommended gas limit for deployment or ``null``.
  1131. */
  1132. readonly gas!: null | bigint;
  1133. /**
  1134. * @private
  1135. */
  1136. constructor(guard: any, type: FragmentType, inputs: ReadonlyArray<ParamType>, payable: boolean, gas: null | bigint) {
  1137. super(guard, type, inputs);
  1138. Object.defineProperty(this, internal, {
  1139. value: ConstructorFragmentInternal,
  1140. });
  1141. defineProperties<ConstructorFragment>(this, { payable, gas });
  1142. }
  1143. /**
  1144. * Returns a string representation of this constructor as %%format%%.
  1145. */
  1146. format(format?: FormatType): string {
  1147. assert(format != null && format !== 'sighash', 'cannot format a constructor for sighash', 'UNSUPPORTED_OPERATION', {
  1148. operation: 'format(sighash)',
  1149. });
  1150. if (format === 'json') {
  1151. return JSON.stringify({
  1152. type: 'constructor',
  1153. stateMutability: this.payable ? 'payable' : 'undefined',
  1154. payable: this.payable,
  1155. gas: this.gas != null ? this.gas : undefined,
  1156. inputs: this.inputs.map((i) => JSON.parse(i.format(format))),
  1157. });
  1158. }
  1159. const result = [`constructor${joinParams(format, this.inputs)}`];
  1160. result.push(this.payable ? 'payable' : 'nonpayable');
  1161. if (this.gas != null) {
  1162. result.push(`@${this.gas.toString()}`);
  1163. }
  1164. return result.join(' ');
  1165. }
  1166. /**
  1167. * Returns a new **ConstructorFragment** for %%obj%%.
  1168. */
  1169. static from(obj: any): ConstructorFragment {
  1170. if (ConstructorFragment.isFragment(obj)) {
  1171. return obj;
  1172. }
  1173. if (typeof obj === 'string') {
  1174. return ConstructorFragment.from(lex(obj));
  1175. } else if (obj instanceof TokenString) {
  1176. consumeKeywords(obj, setify(['constructor']));
  1177. const inputs = consumeParams(obj);
  1178. const payable = !!consumeKeywords(obj, setify(['payable'])).has('payable');
  1179. const gas = consumeGas(obj);
  1180. consumeEoi(obj);
  1181. return new ConstructorFragment(_guard, 'constructor', inputs, payable, gas);
  1182. }
  1183. return new ConstructorFragment(
  1184. _guard,
  1185. 'constructor',
  1186. obj.inputs ? obj.inputs.map(ParamType.from) : [],
  1187. !!obj.payable,
  1188. obj.gas != null ? obj.gas : null
  1189. );
  1190. }
  1191. /**
  1192. * Returns ``true`` and provides a type guard if %%value%% is a
  1193. * **ConstructorFragment**.
  1194. */
  1195. static isFragment(value: any): value is ConstructorFragment {
  1196. return value && value[internal] === ConstructorFragmentInternal;
  1197. }
  1198. }
  1199. /**
  1200. * A Fragment which represents a method.
  1201. */
  1202. export class FallbackFragment extends Fragment {
  1203. /**
  1204. * If the function can be sent value during invocation.
  1205. */
  1206. readonly payable!: boolean;
  1207. constructor(guard: any, inputs: ReadonlyArray<ParamType>, payable: boolean) {
  1208. super(guard, 'fallback', inputs);
  1209. Object.defineProperty(this, internal, { value: FallbackFragmentInternal });
  1210. defineProperties<FallbackFragment>(this, { payable });
  1211. }
  1212. /**
  1213. * Returns a string representation of this fallback as %%format%%.
  1214. */
  1215. format(format?: FormatType): string {
  1216. const type = this.inputs.length === 0 ? 'receive' : 'fallback';
  1217. if (format === 'json') {
  1218. const stateMutability = this.payable ? 'payable' : 'nonpayable';
  1219. return JSON.stringify({ type, stateMutability });
  1220. }
  1221. return `${type}()${this.payable ? ' payable' : ''}`;
  1222. }
  1223. /**
  1224. * Returns a new **FallbackFragment** for %%obj%%.
  1225. */
  1226. static from(obj: any): FallbackFragment {
  1227. if (FallbackFragment.isFragment(obj)) {
  1228. return obj;
  1229. }
  1230. if (typeof obj === 'string') {
  1231. return FallbackFragment.from(lex(obj));
  1232. } else if (obj instanceof TokenString) {
  1233. const errorObj = obj.toString();
  1234. const topIsValid = obj.peekKeyword(setify(['fallback', 'receive']));
  1235. assertArgument(topIsValid, 'type must be fallback or receive', 'obj', errorObj);
  1236. const type = obj.popKeyword(setify(['fallback', 'receive']));
  1237. // receive()
  1238. if (type === 'receive') {
  1239. const inputs = consumeParams(obj);
  1240. assertArgument(inputs.length === 0, `receive cannot have arguments`, 'obj.inputs', inputs);
  1241. consumeKeywords(obj, setify(['payable']));
  1242. consumeEoi(obj);
  1243. return new FallbackFragment(_guard, [], true);
  1244. }
  1245. // fallback() [payable]
  1246. // fallback(bytes) [payable] returns (bytes)
  1247. let inputs = consumeParams(obj);
  1248. if (inputs.length) {
  1249. assertArgument(
  1250. inputs.length === 1 && inputs[0].type === 'bytes',
  1251. 'invalid fallback inputs',
  1252. 'obj.inputs',
  1253. inputs.map((i) => i.format('minimal')).join(', ')
  1254. );
  1255. } else {
  1256. inputs = [ParamType.from('bytes')];
  1257. }
  1258. const mutability = consumeMutability(obj);
  1259. assertArgument(
  1260. mutability === 'nonpayable' || mutability === 'payable',
  1261. 'fallback cannot be constants',
  1262. 'obj.stateMutability',
  1263. mutability
  1264. );
  1265. if (consumeKeywords(obj, setify(['returns'])).has('returns')) {
  1266. const outputs = consumeParams(obj);
  1267. assertArgument(
  1268. outputs.length === 1 && outputs[0].type === 'bytes',
  1269. 'invalid fallback outputs',
  1270. 'obj.outputs',
  1271. outputs.map((i) => i.format('minimal')).join(', ')
  1272. );
  1273. }
  1274. consumeEoi(obj);
  1275. return new FallbackFragment(_guard, inputs, mutability === 'payable');
  1276. }
  1277. if (obj.type === 'receive') {
  1278. return new FallbackFragment(_guard, [], true);
  1279. }
  1280. if (obj.type === 'fallback') {
  1281. const inputs = [ParamType.from('bytes')];
  1282. const payable = obj.stateMutability === 'payable';
  1283. return new FallbackFragment(_guard, inputs, payable);
  1284. }
  1285. assertArgument(false, 'invalid fallback description', 'obj', obj);
  1286. }
  1287. /**
  1288. * Returns ``true`` and provides a type guard if %%value%% is a
  1289. * **FallbackFragment**.
  1290. */
  1291. static isFragment(value: any): value is FallbackFragment {
  1292. return value && value[internal] === FallbackFragmentInternal;
  1293. }
  1294. }
  1295. /**
  1296. * A Fragment which represents a method.
  1297. */
  1298. export class FunctionFragment extends NamedFragment {
  1299. /**
  1300. * If the function is constant (e.g. ``pure`` or ``view`` functions).
  1301. */
  1302. readonly constant!: boolean;
  1303. /**
  1304. * The returned types for the result of calling this function.
  1305. */
  1306. readonly outputs!: ReadonlyArray<ParamType>;
  1307. /**
  1308. * The state mutability (e.g. ``payable``, ``nonpayable``, ``view``
  1309. * or ``pure``)
  1310. */
  1311. readonly stateMutability!: 'payable' | 'nonpayable' | 'view' | 'pure';
  1312. /**
  1313. * If the function can be sent value during invocation.
  1314. */
  1315. readonly payable!: boolean;
  1316. /**
  1317. * The recommended gas limit to send when calling this function.
  1318. */
  1319. readonly gas!: null | bigint;
  1320. /**
  1321. * @private
  1322. */
  1323. constructor(
  1324. guard: any,
  1325. name: string,
  1326. stateMutability: 'payable' | 'nonpayable' | 'view' | 'pure',
  1327. inputs: ReadonlyArray<ParamType>,
  1328. outputs: ReadonlyArray<ParamType>,
  1329. gas: null | bigint
  1330. ) {
  1331. super(guard, 'function', name, inputs);
  1332. Object.defineProperty(this, internal, { value: FunctionFragmentInternal });
  1333. outputs = Object.freeze(outputs.slice());
  1334. const constant = stateMutability === 'view' || stateMutability === 'pure';
  1335. const payable = stateMutability === 'payable';
  1336. defineProperties<FunctionFragment>(this, {
  1337. constant,
  1338. gas,
  1339. outputs,
  1340. payable,
  1341. stateMutability,
  1342. });
  1343. }
  1344. /**
  1345. * The Function selector.
  1346. */
  1347. get selector(): string {
  1348. return id(this.format('sighash')).substring(0, 10);
  1349. }
  1350. /**
  1351. * Returns a string representation of this function as %%format%%.
  1352. */
  1353. format(format?: FormatType): string {
  1354. if (format == null) {
  1355. format = 'sighash';
  1356. }
  1357. if (format === 'json') {
  1358. return JSON.stringify({
  1359. type: 'function',
  1360. name: this.name,
  1361. constant: this.constant,
  1362. stateMutability: this.stateMutability !== 'nonpayable' ? this.stateMutability : undefined,
  1363. payable: this.payable,
  1364. gas: this.gas != null ? this.gas : undefined,
  1365. inputs: this.inputs.map((i) => JSON.parse(i.format(format))),
  1366. outputs: this.outputs.map((o) => JSON.parse(o.format(format))),
  1367. });
  1368. }
  1369. const result: string[] = [];
  1370. if (format !== 'sighash') {
  1371. result.push('function');
  1372. }
  1373. result.push(this.name + joinParams(format, this.inputs));
  1374. if (format !== 'sighash') {
  1375. if (this.stateMutability !== 'nonpayable') {
  1376. result.push(this.stateMutability);
  1377. }
  1378. if (this.outputs && this.outputs.length) {
  1379. result.push('returns');
  1380. result.push(joinParams(format, this.outputs));
  1381. }
  1382. if (this.gas != null) {
  1383. result.push(`@${this.gas.toString()}`);
  1384. }
  1385. }
  1386. return result.join(' ');
  1387. }
  1388. /**
  1389. * Return the selector for a function with %%name%% and %%params%%.
  1390. */
  1391. static getSelector(name: string, params?: Array<any>): string {
  1392. params = (params || []).map((p) => ParamType.from(p));
  1393. const fragment = new FunctionFragment(_guard, name, 'view', params, [], null);
  1394. return fragment.selector;
  1395. }
  1396. /**
  1397. * Returns a new **FunctionFragment** for %%obj%%.
  1398. */
  1399. static from(obj: any): FunctionFragment {
  1400. if (FunctionFragment.isFragment(obj)) {
  1401. return obj;
  1402. }
  1403. if (typeof obj === 'string') {
  1404. return FunctionFragment.from(lex(obj));
  1405. } else if (obj instanceof TokenString) {
  1406. const name = consumeName('function', obj);
  1407. const inputs = consumeParams(obj);
  1408. const mutability = consumeMutability(obj);
  1409. let outputs: Array<ParamType> = [];
  1410. if (consumeKeywords(obj, setify(['returns'])).has('returns')) {
  1411. outputs = consumeParams(obj);
  1412. }
  1413. const gas = consumeGas(obj);
  1414. consumeEoi(obj);
  1415. return new FunctionFragment(_guard, name, mutability, inputs, outputs, gas);
  1416. }
  1417. let stateMutability = obj.stateMutability;
  1418. // Use legacy Solidity ABI logic if stateMutability is missing
  1419. if (stateMutability == null) {
  1420. stateMutability = 'payable';
  1421. if (typeof obj.constant === 'boolean') {
  1422. stateMutability = 'view';
  1423. if (!obj.constant) {
  1424. stateMutability = 'payable';
  1425. if (typeof obj.payable === 'boolean' && !obj.payable) {
  1426. stateMutability = 'nonpayable';
  1427. }
  1428. }
  1429. } else if (typeof obj.payable === 'boolean' && !obj.payable) {
  1430. stateMutability = 'nonpayable';
  1431. }
  1432. }
  1433. // @TODO: verifyState for stateMutability (e.g. throw if
  1434. // payable: false but stateMutability is "nonpayable")
  1435. return new FunctionFragment(
  1436. _guard,
  1437. obj.name,
  1438. stateMutability,
  1439. obj.inputs ? obj.inputs.map(ParamType.from) : [],
  1440. obj.outputs ? obj.outputs.map(ParamType.from) : [],
  1441. obj.gas != null ? obj.gas : null
  1442. );
  1443. }
  1444. /**
  1445. * Returns ``true`` and provides a type guard if %%value%% is a
  1446. * **FunctionFragment**.
  1447. */
  1448. static isFragment(value: any): value is FunctionFragment {
  1449. return value && value[internal] === FunctionFragmentInternal;
  1450. }
  1451. }
  1452. /**
  1453. * A Fragment which represents a structure.
  1454. */
  1455. export class StructFragment extends NamedFragment {
  1456. /**
  1457. * @private
  1458. */
  1459. constructor(guard: any, name: string, inputs: ReadonlyArray<ParamType>) {
  1460. super(guard, 'struct', name, inputs);
  1461. Object.defineProperty(this, internal, { value: StructFragmentInternal });
  1462. }
  1463. /**
  1464. * Returns a string representation of this struct as %%format%%.
  1465. */
  1466. format(): string {
  1467. throw new Error('@TODO');
  1468. }
  1469. /**
  1470. * Returns a new **StructFragment** for %%obj%%.
  1471. */
  1472. static from(obj: any): StructFragment {
  1473. if (typeof obj === 'string') {
  1474. return StructFragment.from(lex(obj));
  1475. } else if (obj instanceof TokenString) {
  1476. const name = consumeName('struct', obj);
  1477. const inputs = consumeParams(obj);
  1478. consumeEoi(obj);
  1479. return new StructFragment(_guard, name, inputs);
  1480. }
  1481. return new StructFragment(_guard, obj.name, obj.inputs ? obj.inputs.map(ParamType.from) : []);
  1482. }
  1483. // @TODO: fix this return type
  1484. /**
  1485. * Returns ``true`` and provides a type guard if %%value%% is a
  1486. * **StructFragment**.
  1487. */
  1488. static isFragment(value: any): value is FunctionFragment {
  1489. return value && value[internal] === StructFragmentInternal;
  1490. }
  1491. }