UNPKG

48.3 kBPlain TextView Raw
1"use strict";
2
3import { checkResultErrors, EventFragment, Fragment, FunctionFragment, Indexed, Interface, JsonFragment, LogDescription, ParamType, Result } from "@ethersproject/abi";
4import { Block, BlockTag, Filter, FilterByBlockHash, Listener, Log, Provider, TransactionReceipt, TransactionRequest, TransactionResponse } from "@ethersproject/abstract-provider";
5import { Signer, VoidSigner } from "@ethersproject/abstract-signer";
6import { getAddress, getContractAddress } from "@ethersproject/address";
7import { BigNumber, BigNumberish } from "@ethersproject/bignumber";
8import { arrayify, BytesLike, concat, hexlify, isBytes, isHexString } from "@ethersproject/bytes";
9import { Deferrable, defineReadOnly, deepCopy, getStatic, resolveProperties, shallowCopy } from "@ethersproject/properties";
10import { AccessList, accessListify, AccessListish } from "@ethersproject/transactions";
11
12import { Logger } from "@ethersproject/logger";
13import { version } from "./_version";
14
15const logger = new Logger(version);
16
17export interface Overrides {
18 gasLimit?: BigNumberish | Promise<BigNumberish>;
19 gasPrice?: BigNumberish | Promise<BigNumberish>;
20 maxFeePerGas?: BigNumberish | Promise<BigNumberish>;
21 maxPriorityFeePerGas?: BigNumberish | Promise<BigNumberish>;
22 nonce?: BigNumberish | Promise<BigNumberish>;
23 type?: number;
24 accessList?: AccessListish;
25 customData?: Record<string, any>;
26 ccipReadEnabled?: boolean;
27};
28
29export interface PayableOverrides extends Overrides {
30 value?: BigNumberish | Promise<BigNumberish>;
31}
32
33export interface CallOverrides extends PayableOverrides {
34 blockTag?: BlockTag | Promise<BlockTag>;
35 from?: string | Promise<string>;
36}
37
38// @TODO: Better hierarchy with: (in v6)
39// - abstract-provider:TransactionRequest
40// - transactions:Transaction
41// - transaction:UnsignedTransaction
42
43export interface PopulatedTransaction {
44 to?: string;
45 from?: string;
46 nonce?: number;
47
48 gasLimit?: BigNumber;
49 gasPrice?: BigNumber;
50
51 data?: string;
52 value?: BigNumber;
53 chainId?: number;
54
55 type?: number;
56 accessList?: AccessList;
57
58 maxFeePerGas?: BigNumber;
59 maxPriorityFeePerGas?: BigNumber;
60
61 customData?: Record<string, any>;
62 ccipReadEnabled?: boolean;
63};
64
65export type EventFilter = {
66 address?: string;
67 topics?: Array<string|Array<string>>;
68};
69
70
71export type ContractFunction<T = any> = (...args: Array<any>) => Promise<T>;
72
73
74// The (n + 1)th parameter passed to contract event callbacks
75export interface Event extends Log {
76
77 // The event name
78 event?: string;
79
80 // The event signature
81 eventSignature?: string;
82
83 // The parsed arguments to the event
84 args?: Result;
85
86 // If parsing the arguments failed, this is the error
87 decodeError?: Error;
88
89 // A function that can be used to decode event data and topics
90 decode?: (data: string, topics?: Array<string>) => any;
91
92 // A function that will remove the listener responsible for this event (if any)
93 removeListener: () => void;
94
95 // Get blockchain details about this event's block and transaction
96 getBlock: () => Promise<Block>;
97 getTransaction: () => Promise<TransactionResponse>;
98 getTransactionReceipt: () => Promise<TransactionReceipt>;
99}
100
101export interface ContractReceipt extends TransactionReceipt {
102 events?: Array<Event>;
103}
104
105export interface ContractTransaction extends TransactionResponse {
106 wait(confirmations?: number): Promise<ContractReceipt>;
107}
108
109///////////////////////////////
110
111const allowedTransactionKeys: { [ key: string ]: boolean } = {
112 chainId: true, data: true, from: true, gasLimit: true, gasPrice:true, nonce: true, to: true, value: true,
113 type: true, accessList: true,
114 maxFeePerGas: true, maxPriorityFeePerGas: true,
115 customData: true,
116 ccipReadEnabled: true
117}
118
119async function resolveName(resolver: Signer | Provider, nameOrPromise: string | Promise<string>): Promise<string> {
120 const name = await nameOrPromise;
121
122 if (typeof(name) !== "string") {
123 logger.throwArgumentError("invalid address or ENS name", "name", name);
124 }
125
126 // If it is already an address, just use it (after adding checksum)
127 try {
128 return getAddress(name);
129 } catch (error) { }
130
131 if (!resolver) {
132 logger.throwError("a provider or signer is needed to resolve ENS names", Logger.errors.UNSUPPORTED_OPERATION, {
133 operation: "resolveName"
134 });
135 }
136
137 const address = await resolver.resolveName(name);
138
139 if (address == null) {
140 logger.throwArgumentError("resolver or addr is not configured for ENS name", "name", name);
141 }
142
143 return address;
144}
145
146// Recursively replaces ENS names with promises to resolve the name and resolves all properties
147async function resolveAddresses(resolver: Signer | Provider, value: any, paramType: ParamType | Array<ParamType>): Promise<any> {
148 if (Array.isArray(paramType)) {
149 return await Promise.all(paramType.map((paramType, index) => {
150 return resolveAddresses(
151 resolver,
152 ((Array.isArray(value)) ? value[index]: value[paramType.name]),
153 paramType
154 );
155 }));
156 }
157
158 if (paramType.type === "address") {
159 return await resolveName(resolver, value);
160 }
161
162 if (paramType.type === "tuple") {
163 return await resolveAddresses(resolver, value, paramType.components);
164 }
165
166 if (paramType.baseType === "array") {
167 if (!Array.isArray(value)) {
168 return Promise.reject(logger.makeError("invalid value for array", Logger.errors.INVALID_ARGUMENT, {
169 argument: "value",
170 value
171 }));
172 }
173 return await Promise.all(value.map((v) => resolveAddresses(resolver, v, paramType.arrayChildren)));
174 }
175
176 return value;
177}
178
179async function populateTransaction(contract: Contract, fragment: FunctionFragment, args: Array<any>): Promise<PopulatedTransaction> {
180 // If an extra argument is given, it is overrides
181 let overrides: CallOverrides = { };
182 if (args.length === fragment.inputs.length + 1 && typeof(args[args.length - 1]) === "object") {
183 overrides = shallowCopy(args.pop());
184 }
185
186 // Make sure the parameter count matches
187 logger.checkArgumentCount(args.length, fragment.inputs.length, "passed to contract");
188
189 // Populate "from" override (allow promises)
190 if (contract.signer) {
191 if (overrides.from) {
192 // Contracts with a Signer are from the Signer's frame-of-reference;
193 // but we allow overriding "from" if it matches the signer
194 overrides.from = resolveProperties({
195 override: resolveName(contract.signer, overrides.from),
196 signer: contract.signer.getAddress()
197 }).then(async (check) => {
198 if (getAddress(check.signer) !== check.override) {
199 logger.throwError("Contract with a Signer cannot override from", Logger.errors.UNSUPPORTED_OPERATION, {
200 operation: "overrides.from"
201 });
202 }
203
204 return check.override;
205 });
206
207 } else {
208 overrides.from = contract.signer.getAddress();
209 }
210
211 } else if (overrides.from) {
212 overrides.from = resolveName(contract.provider, overrides.from);
213
214 //} else {
215 // Contracts without a signer can override "from", and if
216 // unspecified the zero address is used
217 //overrides.from = AddressZero;
218 }
219
220 // Wait for all dependencies to be resolved (prefer the signer over the provider)
221 const resolved = await resolveProperties({
222 args: resolveAddresses(contract.signer || contract.provider, args, fragment.inputs),
223 address: contract.resolvedAddress,
224 overrides: (resolveProperties(overrides) || { })
225 });
226
227 // The ABI coded transaction
228 const data = contract.interface.encodeFunctionData(fragment, resolved.args);
229 const tx: PopulatedTransaction = {
230 data: data,
231 to: resolved.address
232 };
233
234 // Resolved Overrides
235 const ro = resolved.overrides;
236
237 // Populate simple overrides
238 if (ro.nonce != null) { tx.nonce = BigNumber.from(ro.nonce).toNumber(); }
239 if (ro.gasLimit != null) { tx.gasLimit = BigNumber.from(ro.gasLimit); }
240 if (ro.gasPrice != null) { tx.gasPrice = BigNumber.from(ro.gasPrice); }
241 if (ro.maxFeePerGas != null) { tx.maxFeePerGas = BigNumber.from(ro.maxFeePerGas); }
242 if (ro.maxPriorityFeePerGas != null) { tx.maxPriorityFeePerGas = BigNumber.from(ro.maxPriorityFeePerGas); }
243 if (ro.from != null) { tx.from = ro.from; }
244
245 if (ro.type != null) { tx.type = ro.type; }
246 if (ro.accessList != null) { tx.accessList = accessListify(ro.accessList); }
247
248 // If there was no "gasLimit" override, but the ABI specifies a default, use it
249 if (tx.gasLimit == null && fragment.gas != null) {
250 // Compute the intrinsic gas cost for this transaction
251 // @TODO: This is based on the yellow paper as of Petersburg; this is something
252 // we may wish to parameterize in v6 as part of the Network object. Since this
253 // is always a non-nil to address, we can ignore G_create, but may wish to add
254 // similar logic to the ContractFactory.
255 let intrinsic = 21000;
256 const bytes = arrayify(data);
257 for (let i = 0; i < bytes.length; i++) {
258 intrinsic += 4;
259 if (bytes[i]) { intrinsic += 64; }
260 }
261 tx.gasLimit = BigNumber.from(fragment.gas).add(intrinsic);
262 }
263
264 // Populate "value" override
265 if (ro.value) {
266 const roValue = BigNumber.from(ro.value);
267 if (!roValue.isZero() && !fragment.payable) {
268 logger.throwError("non-payable method cannot override value", Logger.errors.UNSUPPORTED_OPERATION, {
269 operation: "overrides.value",
270 value: overrides.value
271 });
272 }
273 tx.value = roValue;
274 }
275
276 if (ro.customData) {
277 tx.customData = shallowCopy(ro.customData);
278 }
279
280 if (ro.ccipReadEnabled) {
281 tx.ccipReadEnabled = !!ro.ccipReadEnabled;
282 }
283
284 // Remove the overrides
285 delete overrides.nonce;
286 delete overrides.gasLimit;
287 delete overrides.gasPrice;
288 delete overrides.from;
289 delete overrides.value;
290
291 delete overrides.type;
292 delete overrides.accessList;
293
294 delete overrides.maxFeePerGas;
295 delete overrides.maxPriorityFeePerGas;
296
297 delete overrides.customData;
298 delete overrides.ccipReadEnabled;
299
300 // Make sure there are no stray overrides, which may indicate a
301 // typo or using an unsupported key.
302 const leftovers = Object.keys(overrides).filter((key) => ((<any>overrides)[key] != null));
303 if (leftovers.length) {
304 logger.throwError(`cannot override ${ leftovers.map((l) => JSON.stringify(l)).join(",") }`, Logger.errors.UNSUPPORTED_OPERATION, {
305 operation: "overrides",
306 overrides: leftovers
307 });
308 }
309
310 return tx;
311}
312
313
314function buildPopulate(contract: Contract, fragment: FunctionFragment): ContractFunction<PopulatedTransaction> {
315 return function(...args: Array<any>): Promise<PopulatedTransaction> {
316 return populateTransaction(contract, fragment, args);
317 };
318}
319
320function buildEstimate(contract: Contract, fragment: FunctionFragment): ContractFunction<BigNumber> {
321 const signerOrProvider = (contract.signer || contract.provider);
322 return async function(...args: Array<any>): Promise<BigNumber> {
323 if (!signerOrProvider) {
324 logger.throwError("estimate require a provider or signer", Logger.errors.UNSUPPORTED_OPERATION, {
325 operation: "estimateGas"
326 })
327 }
328
329 const tx = await populateTransaction(contract, fragment, args);
330 return await signerOrProvider.estimateGas(tx);
331 };
332}
333
334function addContractWait(contract: Contract, tx: TransactionResponse) {
335 const wait = tx.wait.bind(tx);
336 tx.wait = (confirmations?: number) => {
337 return wait(confirmations).then((receipt: ContractReceipt) => {
338 receipt.events = receipt.logs.map((log) => {
339 let event: Event = (<Event>deepCopy(log));
340 let parsed: LogDescription = null;
341 try {
342 parsed = contract.interface.parseLog(log);
343 } catch (e){ }
344
345 // Successfully parsed the event log; include it
346 if (parsed) {
347 event.args = parsed.args;
348 event.decode = (data: BytesLike, topics?: Array<any>) => {
349 return contract.interface.decodeEventLog(parsed.eventFragment, data, topics);
350 };
351 event.event = parsed.name;
352 event.eventSignature = parsed.signature;
353 }
354
355 // Useful operations
356 event.removeListener = () => { return contract.provider; }
357 event.getBlock = () => {
358 return contract.provider.getBlock(receipt.blockHash);
359 }
360 event.getTransaction = () => {
361 return contract.provider.getTransaction(receipt.transactionHash);
362 }
363 event.getTransactionReceipt = () => {
364 return Promise.resolve(receipt);
365 }
366
367 return event;
368 });
369
370 return receipt;
371 });
372 };
373}
374
375function buildCall(contract: Contract, fragment: FunctionFragment, collapseSimple: boolean): ContractFunction {
376 const signerOrProvider = (contract.signer || contract.provider);
377
378 return async function(...args: Array<any>): Promise<any> {
379 // Extract the "blockTag" override if present
380 let blockTag = undefined;
381 if (args.length === fragment.inputs.length + 1 && typeof(args[args.length - 1]) === "object") {
382 const overrides = shallowCopy(args.pop());
383 if (overrides.blockTag != null) {
384 blockTag = await overrides.blockTag;
385 }
386 delete overrides.blockTag;
387 args.push(overrides);
388 }
389
390 // If the contract was just deployed, wait until it is mined
391 if (contract.deployTransaction != null) {
392 await contract._deployed(blockTag);
393 }
394
395 // Call a node and get the result
396 const tx = await populateTransaction(contract, fragment, args);
397 const result = await signerOrProvider.call(tx, blockTag);
398
399 try {
400 let value = contract.interface.decodeFunctionResult(fragment, result);
401 if (collapseSimple && fragment.outputs.length === 1) {
402 value = value[0];
403 }
404 return value;
405
406 } catch (error) {
407 if (error.code === Logger.errors.CALL_EXCEPTION) {
408 error.address = contract.address;
409 error.args = args;
410 error.transaction = tx;
411 }
412 throw error;
413 }
414 };
415}
416
417function buildSend(contract: Contract, fragment: FunctionFragment): ContractFunction<TransactionResponse> {
418 return async function(...args: Array<any>): Promise<TransactionResponse> {
419 if (!contract.signer) {
420 logger.throwError("sending a transaction requires a signer", Logger.errors.UNSUPPORTED_OPERATION, {
421 operation: "sendTransaction"
422 })
423 }
424
425 // If the contract was just deployed, wait until it is mined
426 if (contract.deployTransaction != null) {
427 await contract._deployed();
428 }
429
430 const txRequest = await populateTransaction(contract, fragment, args);
431
432 const tx = await contract.signer.sendTransaction(txRequest);
433
434 // Tweak the tx.wait so the receipt has extra properties
435 addContractWait(contract, tx);
436
437 return tx;
438 };
439}
440
441function buildDefault(contract: Contract, fragment: FunctionFragment, collapseSimple: boolean): ContractFunction {
442 if (fragment.constant) {
443 return buildCall(contract, fragment, collapseSimple);
444 }
445 return buildSend(contract, fragment);
446}
447
448function getEventTag(filter: EventFilter): string {
449 if (filter.address && (filter.topics == null || filter.topics.length === 0)) {
450 return "*";
451 }
452
453 return (filter.address || "*") + "@" + (filter.topics ? filter.topics.map((topic) => {
454 if (Array.isArray(topic)) {
455 return topic.join("|");
456 }
457 return topic;
458 }).join(":"): "");
459}
460
461class RunningEvent {
462 readonly tag: string;
463 readonly filter: EventFilter;
464 private _listeners: Array<{ listener: Listener, once: boolean }>;
465
466 constructor(tag: string, filter: EventFilter) {
467 defineReadOnly(this, "tag", tag);
468 defineReadOnly(this, "filter", filter);
469 this._listeners = [ ];
470 }
471
472 addListener(listener: Listener, once: boolean): void {
473 this._listeners.push({ listener: listener, once: once });
474 }
475
476 removeListener(listener: Listener): void {
477 let done = false;
478 this._listeners = this._listeners.filter((item) => {
479 if (done || item.listener !== listener) { return true; }
480 done = true;
481 return false;
482 });
483 }
484
485 removeAllListeners(): void {
486 this._listeners = [];
487 }
488
489 listeners(): Array<Listener> {
490 return this._listeners.map((i) => i.listener);
491 }
492
493 listenerCount(): number {
494 return this._listeners.length;
495 }
496
497 run(args: Array<any>): number {
498 const listenerCount = this.listenerCount();
499 this._listeners = this._listeners.filter((item) => {
500
501 const argsCopy = args.slice();
502
503 // Call the callback in the next event loop
504 setTimeout(() => {
505 item.listener.apply(this, argsCopy);
506 }, 0);
507
508 // Reschedule it if it not "once"
509 return !(item.once);
510 });
511
512 return listenerCount;
513 }
514
515 prepareEvent(event: Event): void {
516 }
517
518 // Returns the array that will be applied to an emit
519 getEmit(event: Event): Array<any> {
520 return [ event ];
521 }
522}
523
524class ErrorRunningEvent extends RunningEvent {
525 constructor() {
526 super("error", null);
527 }
528}
529
530
531// @TODO Fragment should inherit Wildcard? and just override getEmit?
532// or have a common abstract super class, with enough constructor
533// options to configure both.
534
535// A Fragment Event will populate all the properties that Wildcard
536// will, and additionally dereference the arguments when emitting
537class FragmentRunningEvent extends RunningEvent {
538 readonly address: string;
539 readonly interface: Interface;
540 readonly fragment: EventFragment;
541
542 constructor(address: string, contractInterface: Interface, fragment: EventFragment, topics?: Array<string|Array<string>>) {
543 const filter: EventFilter = {
544 address: address
545 }
546
547 let topic = contractInterface.getEventTopic(fragment);
548 if (topics) {
549 if (topic !== topics[0]) { logger.throwArgumentError("topic mismatch", "topics", topics); }
550 filter.topics = topics.slice();
551 } else {
552 filter.topics = [ topic ];
553 }
554
555 super(getEventTag(filter), filter);
556 defineReadOnly(this, "address", address);
557 defineReadOnly(this, "interface", contractInterface);
558 defineReadOnly(this, "fragment", fragment);
559 }
560
561
562 prepareEvent(event: Event): void {
563 super.prepareEvent(event);
564
565 event.event = this.fragment.name;
566 event.eventSignature = this.fragment.format();
567
568 event.decode = (data: BytesLike, topics?: Array<string>) => {
569 return this.interface.decodeEventLog(this.fragment, data, topics);
570 };
571
572 try {
573 event.args = this.interface.decodeEventLog(this.fragment, event.data, event.topics);
574 } catch (error) {
575 event.args = null;
576 event.decodeError = error;
577 }
578 }
579
580 getEmit(event: Event): Array<any> {
581 const errors = checkResultErrors(event.args);
582 if (errors.length) { throw errors[0].error; }
583
584 const args = (event.args || []).slice();
585 args.push(event);
586 return args;
587 }
588}
589
590// A Wildcard Event will attempt to populate:
591// - event The name of the event name
592// - eventSignature The full signature of the event
593// - decode A function to decode data and topics
594// - args The decoded data and topics
595class WildcardRunningEvent extends RunningEvent {
596 readonly address: string;
597 readonly interface: Interface;
598
599 constructor(address: string, contractInterface: Interface) {
600 super("*", { address: address });
601 defineReadOnly(this, "address", address);
602 defineReadOnly(this, "interface", contractInterface);
603 }
604
605 prepareEvent(event: Event): void {
606 super.prepareEvent(event);
607
608 try {
609 const parsed = this.interface.parseLog(event);
610 event.event = parsed.name;
611 event.eventSignature = parsed.signature;
612
613 event.decode = (data: BytesLike, topics?: Array<string>) => {
614 return this.interface.decodeEventLog(parsed.eventFragment, data, topics);
615 };
616
617 event.args = parsed.args;
618 } catch (error) {
619 // No matching event
620 }
621 }
622}
623
624export type ContractInterface = string | ReadonlyArray<Fragment | JsonFragment | string> | Interface;
625
626type InterfaceFunc = (contractInterface: ContractInterface) => Interface;
627
628
629export class BaseContract {
630 readonly address: string;
631 readonly interface: Interface;
632
633 readonly signer: Signer;
634 readonly provider: Provider;
635
636 readonly functions: { [ name: string ]: ContractFunction };
637
638 readonly callStatic: { [ name: string ]: ContractFunction };
639 readonly estimateGas: { [ name: string ]: ContractFunction<BigNumber> };
640 readonly populateTransaction: { [ name: string ]: ContractFunction<PopulatedTransaction> };
641
642 readonly filters: { [ name: string ]: (...args: Array<any>) => EventFilter };
643
644 // This will always be an address. This will only differ from
645 // address if an ENS name was used in the constructor
646 readonly resolvedAddress: Promise<string>;
647
648 // This is only set if the contract was created with a call to deploy
649 readonly deployTransaction: TransactionResponse;
650
651 _deployedPromise: Promise<Contract>;
652
653 // A list of RunningEvents to track listeners for each event tag
654 _runningEvents: { [ eventTag: string ]: RunningEvent };
655
656 // Wrapped functions to call emit and allow deregistration from the provider
657 _wrappedEmits: { [ eventTag: string ]: (...args: Array<any>) => void };
658
659 constructor(addressOrName: string, contractInterface: ContractInterface, signerOrProvider?: Signer | Provider) {
660 // @TODO: Maybe still check the addressOrName looks like a valid address or name?
661 //address = getAddress(address);
662 defineReadOnly(this, "interface", getStatic<InterfaceFunc>(new.target, "getInterface")(contractInterface));
663
664 if (signerOrProvider == null) {
665 defineReadOnly(this, "provider", null);
666 defineReadOnly(this, "signer", null);
667 } else if (Signer.isSigner(signerOrProvider)) {
668 defineReadOnly(this, "provider", signerOrProvider.provider || null);
669 defineReadOnly(this, "signer", signerOrProvider);
670 } else if (Provider.isProvider(signerOrProvider)) {
671 defineReadOnly(this, "provider", signerOrProvider);
672 defineReadOnly(this, "signer", null);
673 } else {
674 logger.throwArgumentError("invalid signer or provider", "signerOrProvider", signerOrProvider);
675 }
676
677 defineReadOnly(this, "callStatic", { });
678 defineReadOnly(this, "estimateGas", { });
679 defineReadOnly(this, "functions", { });
680 defineReadOnly(this, "populateTransaction", { });
681
682 defineReadOnly(this, "filters", { });
683
684 {
685 const uniqueFilters: { [ name: string ]: Array<string> } = { };
686 Object.keys(this.interface.events).forEach((eventSignature) => {
687 const event = this.interface.events[eventSignature];
688 defineReadOnly(this.filters, eventSignature, (...args: Array<any>) => {
689 return {
690 address: this.address,
691 topics: this.interface.encodeFilterTopics(event, args)
692 }
693 });
694 if (!uniqueFilters[event.name]) { uniqueFilters[event.name] = [ ]; }
695 uniqueFilters[event.name].push(eventSignature);
696 });
697
698 Object.keys(uniqueFilters).forEach((name) => {
699 const filters = uniqueFilters[name];
700 if (filters.length === 1) {
701 defineReadOnly(this.filters, name, this.filters[filters[0]]);
702 } else {
703 logger.warn(`Duplicate definition of ${ name } (${ filters.join(", ")})`);
704 }
705 });
706 }
707
708 defineReadOnly(this, "_runningEvents", { });
709 defineReadOnly(this, "_wrappedEmits", { });
710
711 if (addressOrName == null) {
712 logger.throwArgumentError("invalid contract address or ENS name", "addressOrName", addressOrName);
713 }
714
715 defineReadOnly(this, "address", addressOrName);
716 if (this.provider) {
717 defineReadOnly(this, "resolvedAddress", resolveName(this.provider, addressOrName));
718 } else {
719 try {
720 defineReadOnly(this, "resolvedAddress", Promise.resolve(getAddress(addressOrName)));
721 } catch (error) {
722 // Without a provider, we cannot use ENS names
723 logger.throwError("provider is required to use ENS name as contract address", Logger.errors.UNSUPPORTED_OPERATION, {
724 operation: "new Contract"
725 });
726 }
727 }
728
729 // Swallow bad ENS names to prevent Unhandled Exceptions
730 this.resolvedAddress.catch((e) => { });
731
732 const uniqueNames: { [ name: string ]: Array<string> } = { };
733 const uniqueSignatures: { [ signature: string ]: boolean } = { };
734 Object.keys(this.interface.functions).forEach((signature) => {
735 const fragment = this.interface.functions[signature];
736
737 // Check that the signature is unique; if not the ABI generation has
738 // not been cleaned or may be incorrectly generated
739 if (uniqueSignatures[signature]) {
740 logger.warn(`Duplicate ABI entry for ${ JSON.stringify(signature) }`);
741 return;
742 }
743 uniqueSignatures[signature] = true;
744
745 // Track unique names; we only expose bare named functions if they
746 // are ambiguous
747 {
748 const name = fragment.name;
749 if (!uniqueNames[`%${ name }`]) { uniqueNames[`%${ name }`] = [ ]; }
750 uniqueNames[`%${ name }`].push(signature);
751 }
752
753 if ((<Contract>this)[signature] == null) {
754 defineReadOnly<any, any>(this, signature, buildDefault(this, fragment, true));
755 }
756
757 // We do not collapse simple calls on this bucket, which allows
758 // frameworks to safely use this without introspection as well as
759 // allows decoding error recovery.
760 if (this.functions[signature] == null) {
761 defineReadOnly(this.functions, signature, buildDefault(this, fragment, false));
762 }
763
764 if (this.callStatic[signature] == null) {
765 defineReadOnly(this.callStatic, signature, buildCall(this, fragment, true));
766 }
767
768 if (this.populateTransaction[signature] == null) {
769 defineReadOnly(this.populateTransaction, signature, buildPopulate(this, fragment));
770 }
771
772 if (this.estimateGas[signature] == null) {
773 defineReadOnly(this.estimateGas, signature, buildEstimate(this, fragment));
774 }
775 });
776
777 Object.keys(uniqueNames).forEach((name) => {
778 // Ambiguous names to not get attached as bare names
779 const signatures = uniqueNames[name];
780 if (signatures.length > 1) { return; }
781
782 // Strip off the leading "%" used for prototype protection
783 name = name.substring(1);
784
785 const signature = signatures[0];
786
787 // If overwriting a member property that is null, swallow the error
788 try {
789 if ((<Contract>this)[name] == null) {
790 defineReadOnly(<Contract>this, name, (<Contract>this)[signature]);
791 }
792 } catch (e) { }
793
794 if (this.functions[name] == null) {
795 defineReadOnly(this.functions, name, this.functions[signature]);
796 }
797
798 if (this.callStatic[name] == null) {
799 defineReadOnly(this.callStatic, name, this.callStatic[signature]);
800 }
801
802 if (this.populateTransaction[name] == null) {
803 defineReadOnly(this.populateTransaction, name, this.populateTransaction[signature]);
804 }
805
806 if (this.estimateGas[name] == null) {
807 defineReadOnly(this.estimateGas, name, this.estimateGas[signature]);
808 }
809 });
810 }
811
812 static getContractAddress(transaction: { from: string, nonce: BigNumberish }): string {
813 return getContractAddress(transaction);
814 }
815
816 static getInterface(contractInterface: ContractInterface): Interface {
817 if (Interface.isInterface(contractInterface)) {
818 return contractInterface;
819 }
820 return new Interface(contractInterface);
821 }
822
823 // @TODO: Allow timeout?
824 deployed(): Promise<Contract> {
825 return this._deployed();
826 }
827
828 _deployed(blockTag?: BlockTag): Promise<Contract> {
829 if (!this._deployedPromise) {
830
831 // If we were just deployed, we know the transaction we should occur in
832 if (this.deployTransaction) {
833 this._deployedPromise = this.deployTransaction.wait().then(() => {
834 return this;
835 });
836
837 } else {
838 // @TODO: Once we allow a timeout to be passed in, we will wait
839 // up to that many blocks for getCode
840
841 // Otherwise, poll for our code to be deployed
842 this._deployedPromise = this.provider.getCode(this.address, blockTag).then((code) => {
843 if (code === "0x") {
844 logger.throwError("contract not deployed", Logger.errors.UNSUPPORTED_OPERATION, {
845 contractAddress: this.address,
846 operation: "getDeployed"
847 });
848 }
849 return this;
850 });
851 }
852 }
853
854 return this._deployedPromise;
855 }
856
857 // @TODO:
858 // estimateFallback(overrides?: TransactionRequest): Promise<BigNumber>
859
860 // @TODO:
861 // estimateDeploy(bytecode: string, ...args): Promise<BigNumber>
862
863 fallback(overrides?: TransactionRequest): Promise<TransactionResponse> {
864 if (!this.signer) {
865 logger.throwError("sending a transactions require a signer", Logger.errors.UNSUPPORTED_OPERATION, { operation: "sendTransaction(fallback)" })
866 }
867
868 const tx: Deferrable<TransactionRequest> = shallowCopy(overrides || {});
869
870 ["from", "to"].forEach(function(key) {
871 if ((<any>tx)[key] == null) { return; }
872 logger.throwError("cannot override " + key, Logger.errors.UNSUPPORTED_OPERATION, { operation: key })
873 });
874
875 tx.to = this.resolvedAddress;
876 return this.deployed().then(() => {
877 return this.signer.sendTransaction(tx);
878 });
879 }
880
881 // Reconnect to a different signer or provider
882 connect(signerOrProvider: Signer | Provider | string): Contract {
883 if (typeof(signerOrProvider) === "string") {
884 signerOrProvider = new VoidSigner(signerOrProvider, this.provider);
885 }
886
887 const contract = new (<{ new(...args: any[]): Contract }>(this.constructor))(this.address, this.interface, signerOrProvider);
888 if (this.deployTransaction) {
889 defineReadOnly(contract, "deployTransaction", this.deployTransaction);
890 }
891
892 return contract;
893 }
894
895 // Re-attach to a different on-chain instance of this contract
896 attach(addressOrName: string): Contract {
897 return new (<{ new(...args: any[]): Contract }>(this.constructor))(addressOrName, this.interface, this.signer || this.provider);
898 }
899
900 static isIndexed(value: any): value is Indexed {
901 return Indexed.isIndexed(value);
902 }
903
904 private _normalizeRunningEvent(runningEvent: RunningEvent): RunningEvent {
905 // Already have an instance of this event running; we can re-use it
906 if (this._runningEvents[runningEvent.tag]) {
907 return this._runningEvents[runningEvent.tag];
908 }
909 return runningEvent
910 }
911
912 private _getRunningEvent(eventName: EventFilter | string): RunningEvent {
913 if (typeof(eventName) === "string") {
914
915 // Listen for "error" events (if your contract has an error event, include
916 // the full signature to bypass this special event keyword)
917 if (eventName === "error") {
918 return this._normalizeRunningEvent(new ErrorRunningEvent());
919 }
920
921 // Listen for any event that is registered
922 if (eventName === "event") {
923 return this._normalizeRunningEvent(new RunningEvent("event", null));
924 }
925
926 // Listen for any event
927 if (eventName === "*") {
928 return this._normalizeRunningEvent(new WildcardRunningEvent(this.address, this.interface));
929 }
930
931 // Get the event Fragment (throws if ambiguous/unknown event)
932 const fragment = this.interface.getEvent(eventName)
933 return this._normalizeRunningEvent(new FragmentRunningEvent(this.address, this.interface, fragment));
934 }
935
936 // We have topics to filter by...
937 if (eventName.topics && eventName.topics.length > 0) {
938
939 // Is it a known topichash? (throws if no matching topichash)
940 try {
941 const topic = eventName.topics[0];
942 if (typeof(topic) !== "string") {
943 throw new Error("invalid topic"); // @TODO: May happen for anonymous events
944 }
945 const fragment = this.interface.getEvent(topic);
946 return this._normalizeRunningEvent(new FragmentRunningEvent(this.address, this.interface, fragment, eventName.topics));
947 } catch (error) { }
948
949 // Filter by the unknown topichash
950 const filter: EventFilter = {
951 address: this.address,
952 topics: eventName.topics
953 }
954
955 return this._normalizeRunningEvent(new RunningEvent(getEventTag(filter), filter));
956 }
957
958 return this._normalizeRunningEvent(new WildcardRunningEvent(this.address, this.interface));
959 }
960
961 _checkRunningEvents(runningEvent: RunningEvent): void {
962 if (runningEvent.listenerCount() === 0) {
963 delete this._runningEvents[runningEvent.tag];
964
965 // If we have a poller for this, remove it
966 const emit = this._wrappedEmits[runningEvent.tag];
967 if (emit && runningEvent.filter) {
968 this.provider.off(runningEvent.filter, emit);
969 delete this._wrappedEmits[runningEvent.tag];
970 }
971 }
972 }
973
974 // Subclasses can override this to gracefully recover
975 // from parse errors if they wish
976 _wrapEvent(runningEvent: RunningEvent, log: Log, listener: Listener): Event {
977 const event = <Event>deepCopy(log);
978
979 event.removeListener = () => {
980 if (!listener) { return; }
981 runningEvent.removeListener(listener);
982 this._checkRunningEvents(runningEvent);
983 };
984
985 event.getBlock = () => { return this.provider.getBlock(log.blockHash); }
986 event.getTransaction = () => { return this.provider.getTransaction(log.transactionHash); }
987 event.getTransactionReceipt = () => { return this.provider.getTransactionReceipt(log.transactionHash); }
988
989 // This may throw if the topics and data mismatch the signature
990 runningEvent.prepareEvent(event);
991
992 return event;
993 }
994
995 private _addEventListener(runningEvent: RunningEvent, listener: Listener, once: boolean): void {
996 if (!this.provider) {
997 logger.throwError("events require a provider or a signer with a provider", Logger.errors.UNSUPPORTED_OPERATION, { operation: "once" })
998 }
999
1000 runningEvent.addListener(listener, once);
1001
1002 // Track this running event and its listeners (may already be there; but no hard in updating)
1003 this._runningEvents[runningEvent.tag] = runningEvent;
1004
1005 // If we are not polling the provider, start polling
1006 if (!this._wrappedEmits[runningEvent.tag]) {
1007 const wrappedEmit = (log: Log) => {
1008 let event = this._wrapEvent(runningEvent, log, listener);
1009
1010 // Try to emit the result for the parameterized event...
1011 if (event.decodeError == null) {
1012 try {
1013 const args = runningEvent.getEmit(event);
1014 this.emit(runningEvent.filter, ...args);
1015 } catch (error) {
1016 event.decodeError = error.error;
1017 }
1018 }
1019
1020 // Always emit "event" for fragment-base events
1021 if (runningEvent.filter != null) {
1022 this.emit("event", event);
1023 }
1024
1025 // Emit "error" if there was an error
1026 if (event.decodeError != null) {
1027 this.emit("error", event.decodeError, event);
1028 }
1029 };
1030 this._wrappedEmits[runningEvent.tag] = wrappedEmit;
1031
1032 // Special events, like "error" do not have a filter
1033 if (runningEvent.filter != null) {
1034 this.provider.on(runningEvent.filter, wrappedEmit);
1035 }
1036 }
1037 }
1038
1039 queryFilter(event: EventFilter, fromBlockOrBlockhash?: BlockTag | string, toBlock?: BlockTag): Promise<Array<Event>> {
1040 const runningEvent = this._getRunningEvent(event);
1041 const filter = shallowCopy(runningEvent.filter);
1042
1043 if (typeof(fromBlockOrBlockhash) === "string" && isHexString(fromBlockOrBlockhash, 32)) {
1044 if (toBlock != null) {
1045 logger.throwArgumentError("cannot specify toBlock with blockhash", "toBlock", toBlock);
1046 }
1047 (<FilterByBlockHash>filter).blockHash = fromBlockOrBlockhash;
1048 } else {
1049 (<Filter>filter).fromBlock = ((fromBlockOrBlockhash != null) ? fromBlockOrBlockhash: 0);
1050 (<Filter>filter).toBlock = ((toBlock != null) ? toBlock: "latest");
1051 }
1052
1053 return this.provider.getLogs(filter).then((logs) => {
1054 return logs.map((log) => this._wrapEvent(runningEvent, log, null));
1055 });
1056 }
1057
1058 on(event: EventFilter | string, listener: Listener): this {
1059 this._addEventListener(this._getRunningEvent(event), listener, false);
1060 return this;
1061 }
1062
1063 once(event: EventFilter | string, listener: Listener): this {
1064 this._addEventListener(this._getRunningEvent(event), listener, true);
1065 return this;
1066 }
1067
1068 emit(eventName: EventFilter | string, ...args: Array<any>): boolean {
1069 if (!this.provider) { return false; }
1070
1071 const runningEvent = this._getRunningEvent(eventName);
1072 const result = (runningEvent.run(args) > 0);
1073
1074 // May have drained all the "once" events; check for living events
1075 this._checkRunningEvents(runningEvent);
1076
1077 return result;
1078 }
1079
1080 listenerCount(eventName?: EventFilter | string): number {
1081 if (!this.provider) { return 0; }
1082 if (eventName == null) {
1083 return Object.keys(this._runningEvents).reduce((accum, key) => {
1084 return accum + this._runningEvents[key].listenerCount();
1085 }, 0);
1086 }
1087 return this._getRunningEvent(eventName).listenerCount();
1088 }
1089
1090 listeners(eventName?: EventFilter | string): Array<Listener> {
1091 if (!this.provider) { return []; }
1092
1093 if (eventName == null) {
1094 const result: Array<Listener> = [ ];
1095 for (let tag in this._runningEvents) {
1096 this._runningEvents[tag].listeners().forEach((listener) => {
1097 result.push(listener)
1098 });
1099 }
1100 return result;
1101 }
1102
1103 return this._getRunningEvent(eventName).listeners();
1104 }
1105
1106 removeAllListeners(eventName?: EventFilter | string): this {
1107 if (!this.provider) { return this; }
1108
1109 if (eventName == null) {
1110 for (const tag in this._runningEvents) {
1111 const runningEvent = this._runningEvents[tag];
1112 runningEvent.removeAllListeners();
1113 this._checkRunningEvents(runningEvent);
1114 }
1115 return this;
1116 }
1117
1118 // Delete any listeners
1119 const runningEvent = this._getRunningEvent(eventName);
1120 runningEvent.removeAllListeners();
1121 this._checkRunningEvents(runningEvent);
1122
1123 return this;
1124 }
1125
1126 off(eventName: EventFilter | string, listener: Listener): this {
1127 if (!this.provider) { return this; }
1128 const runningEvent = this._getRunningEvent(eventName);
1129 runningEvent.removeListener(listener);
1130 this._checkRunningEvents(runningEvent);
1131 return this;
1132 }
1133
1134 removeListener(eventName: EventFilter | string, listener: Listener): this {
1135 return this.off(eventName, listener);
1136 }
1137
1138}
1139
1140export class Contract extends BaseContract {
1141 // The meta-class properties
1142 readonly [ key: string ]: ContractFunction | any;
1143}
1144
1145export class ContractFactory {
1146
1147 readonly interface: Interface;
1148 readonly bytecode: string;
1149 readonly signer: Signer;
1150
1151 constructor(contractInterface: ContractInterface, bytecode: BytesLike | { object: string }, signer?: Signer) {
1152
1153 let bytecodeHex: string = null;
1154
1155 if (typeof(bytecode) === "string") {
1156 bytecodeHex = bytecode;
1157 } else if (isBytes(bytecode)) {
1158 bytecodeHex = hexlify(bytecode);
1159 } else if (bytecode && typeof(bytecode.object) === "string") {
1160 // Allow the bytecode object from the Solidity compiler
1161 bytecodeHex = (<any>bytecode).object;
1162 } else {
1163 // Crash in the next verification step
1164 bytecodeHex = "!";
1165 }
1166
1167 // Make sure it is 0x prefixed
1168 if (bytecodeHex.substring(0, 2) !== "0x") { bytecodeHex = "0x" + bytecodeHex; }
1169
1170 // Make sure the final result is valid bytecode
1171 if (!isHexString(bytecodeHex) || (bytecodeHex.length % 2)) {
1172 logger.throwArgumentError("invalid bytecode", "bytecode", bytecode);
1173 }
1174
1175 // If we have a signer, make sure it is valid
1176 if (signer && !Signer.isSigner(signer)) {
1177 logger.throwArgumentError("invalid signer", "signer", signer);
1178 }
1179
1180 defineReadOnly(this, "bytecode", bytecodeHex);
1181 defineReadOnly(this, "interface", getStatic<InterfaceFunc>(new.target, "getInterface")(contractInterface));
1182 defineReadOnly(this, "signer", signer || null);
1183 }
1184
1185 // @TODO: Future; rename to populateTransaction?
1186 getDeployTransaction(...args: Array<any>): TransactionRequest {
1187 let tx: TransactionRequest = { };
1188
1189 // If we have 1 additional argument, we allow transaction overrides
1190 if (args.length === this.interface.deploy.inputs.length + 1 && typeof(args[args.length - 1]) === "object") {
1191 tx = shallowCopy(args.pop());
1192 for (const key in tx) {
1193 if (!allowedTransactionKeys[key]) {
1194 throw new Error("unknown transaction override " + key);
1195 }
1196 }
1197 }
1198
1199 // Do not allow these to be overridden in a deployment transaction
1200 ["data", "from", "to"].forEach((key) => {
1201 if ((<any>tx)[key] == null) { return; }
1202 logger.throwError("cannot override " + key, Logger.errors.UNSUPPORTED_OPERATION, { operation: key })
1203 });
1204
1205 if (tx.value) {
1206 const value = BigNumber.from(tx.value);
1207 if (!value.isZero() && !this.interface.deploy.payable) {
1208 logger.throwError("non-payable constructor cannot override value", Logger.errors.UNSUPPORTED_OPERATION, {
1209 operation: "overrides.value",
1210 value: tx.value
1211 });
1212 }
1213 }
1214
1215 // Make sure the call matches the constructor signature
1216 logger.checkArgumentCount(args.length, this.interface.deploy.inputs.length, " in Contract constructor");
1217
1218 // Set the data to the bytecode + the encoded constructor arguments
1219 tx.data = hexlify(concat([
1220 this.bytecode,
1221 this.interface.encodeDeploy(args)
1222 ]));
1223
1224 return tx
1225 }
1226
1227 async deploy(...args: Array<any>): Promise<Contract> {
1228
1229 let overrides: any = { };
1230
1231 // If 1 extra parameter was passed in, it contains overrides
1232 if (args.length === this.interface.deploy.inputs.length + 1) {
1233 overrides = args.pop();
1234 }
1235
1236 // Make sure the call matches the constructor signature
1237 logger.checkArgumentCount(args.length, this.interface.deploy.inputs.length, " in Contract constructor");
1238
1239 // Resolve ENS names and promises in the arguments
1240 const params = await resolveAddresses(this.signer, args, this.interface.deploy.inputs);
1241 params.push(overrides);
1242
1243 // Get the deployment transaction (with optional overrides)
1244 const unsignedTx = this.getDeployTransaction(...params);
1245
1246 // Send the deployment transaction
1247 const tx = await this.signer.sendTransaction(unsignedTx);
1248
1249 const address = getStatic<(tx: TransactionResponse) => string>(this.constructor, "getContractAddress")(tx);
1250 const contract = getStatic<(address: string, contractInterface: ContractInterface, signer?: Signer) => Contract>(this.constructor, "getContract")(address, this.interface, this.signer);
1251
1252 // Add the modified wait that wraps events
1253 addContractWait(contract, tx);
1254
1255 defineReadOnly(contract, "deployTransaction", tx);
1256 return contract;
1257 }
1258
1259 attach(address: string): Contract {
1260 return (<any>(this.constructor)).getContract(address, this.interface, this.signer);
1261 }
1262
1263 connect(signer: Signer) {
1264 return new (<{ new(...args: any[]): ContractFactory }>(this.constructor))(this.interface, this.bytecode, signer);
1265 }
1266
1267 static fromSolidity(compilerOutput: any, signer?: Signer): ContractFactory {
1268 if (compilerOutput == null) {
1269 logger.throwError("missing compiler output", Logger.errors.MISSING_ARGUMENT, { argument: "compilerOutput" });
1270 }
1271
1272 if (typeof(compilerOutput) === "string") {
1273 compilerOutput = JSON.parse(compilerOutput);
1274 }
1275
1276 const abi = compilerOutput.abi;
1277
1278 let bytecode: any = null;
1279 if (compilerOutput.bytecode) {
1280 bytecode = compilerOutput.bytecode;
1281 } else if (compilerOutput.evm && compilerOutput.evm.bytecode) {
1282 bytecode = compilerOutput.evm.bytecode;
1283 }
1284
1285 return new this(abi, bytecode, signer);
1286 }
1287
1288 static getInterface(contractInterface: ContractInterface) {
1289 return Contract.getInterface(contractInterface);
1290 }
1291
1292 static getContractAddress(tx: { from: string, nonce: BytesLike | BigNumber | number }): string {
1293 return getContractAddress(tx);
1294 }
1295
1296 static getContract(address: string, contractInterface: ContractInterface, signer?: Signer): Contract {
1297 return new Contract(address, contractInterface, signer);
1298 }
1299}