ethers.js/packages/contracts/lib.esm/index.js

934 lines
39 KiB
JavaScript
Raw Normal View History

"use strict";
2020-04-24 06:35:39 +03:00
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
2020-04-25 10:54:54 +03:00
import { checkResultErrors, Indexed, Interface } from "@ethersproject/abi";
import { Provider } from "@ethersproject/abstract-provider";
import { Signer, VoidSigner } from "@ethersproject/abstract-signer";
import { getContractAddress } from "@ethersproject/address";
import { BigNumber } from "@ethersproject/bignumber";
import { concat, hexlify, isBytes, isHexString } from "@ethersproject/bytes";
import { defineReadOnly, deepCopy, getStatic, resolveProperties, shallowCopy } from "@ethersproject/properties";
import { Logger } from "@ethersproject/logger";
import { version } from "./_version";
const logger = new Logger(version);
///////////////////////////////
const allowedTransactionKeys = {
chainId: true, data: true, from: true, gasLimit: true, gasPrice: true, nonce: true, to: true, value: true
};
// Recursively replaces ENS names with promises to resolve the name and resolves all properties
function resolveAddresses(signerOrProvider, value, paramType) {
if (Array.isArray(paramType)) {
return Promise.all(paramType.map((paramType, index) => {
return resolveAddresses(signerOrProvider, ((Array.isArray(value)) ? value[index] : value[paramType.name]), paramType);
}));
}
if (paramType.type === "address") {
return signerOrProvider.resolveName(value);
}
if (paramType.type === "tuple") {
return resolveAddresses(signerOrProvider, value, paramType.components);
}
if (paramType.baseType === "array") {
if (!Array.isArray(value)) {
throw new Error("invalid value for array");
}
return Promise.all(value.map((v) => resolveAddresses(signerOrProvider, v, paramType.arrayChildren)));
}
return Promise.resolve(value);
}
2020-05-30 04:27:59 +03:00
function _populateTransaction(contract, fragment, args, overrides) {
return __awaiter(this, void 0, void 0, function* () {
overrides = shallowCopy(overrides);
// Wait for all dependency addresses to be resolved (prefer the signer over the provider)
const resolved = yield resolveProperties({
args: resolveAddresses(contract.signer || contract.provider, args, fragment.inputs),
address: contract.resolvedAddress,
overrides: (resolveProperties(overrides) || {})
});
// The ABI coded transaction
const tx = {
data: contract.interface.encodeFunctionData(fragment, resolved.args),
to: resolved.address
};
// Resolved Overrides
const ro = resolved.overrides;
if (ro.nonce != null) {
tx.nonce = BigNumber.from(ro.nonce).toNumber();
}
if (ro.gasLimit != null) {
tx.gasLimit = BigNumber.from(ro.gasLimit);
}
if (ro.gasPrice != null) {
tx.gasPrice = BigNumber.from(ro.gasPrice);
}
// If there was no gasLimit override, but the ABI specifies one use it
if (tx.gasLimit == null && fragment.gas != null) {
tx.gasLimit = BigNumber.from(fragment.gas).add(21000);
}
// Remvoe the overrides
delete overrides.nonce;
delete overrides.gasLimit;
delete overrides.gasPrice;
// @TODO: Maybe move all tx property validation to the Signer and Provider?
// Make sure there are no stray overrides, which may indicate a
// typo or using an unsupported key.
const leftovers = Object.keys(overrides);
if (leftovers.length) {
logger.throwError(`cannot override ${leftovers.map((l) => JSON.stringify(l)).join(",")}`, Logger.errors.UNSUPPORTED_OPERATION, {
operation: "overrides",
keys: leftovers
});
}
return tx;
});
2019-11-20 12:57:38 +03:00
}
2020-05-30 04:27:59 +03:00
function populateTransaction(contract, fragment, args, overrides) {
return __awaiter(this, void 0, void 0, function* () {
overrides = shallowCopy(overrides);
// If the contract was just deployed, wait until it is minded
if (contract.deployTransaction != null) {
yield contract._deployed();
}
// Resolved Overrides (keep value for errors)
const ro = yield resolveProperties(overrides);
const value = overrides.value;
delete overrides.value;
const tx = yield _populateTransaction(contract, fragment, args, overrides);
if (ro.value) {
const roValue = BigNumber.from(ro.value);
if (!roValue.isZero() && !fragment.payable) {
logger.throwError("non-payable method cannot override value", Logger.errors.UNSUPPORTED_OPERATION, {
operation: "overrides.value",
value: value
});
}
2020-05-30 04:27:59 +03:00
tx.value = roValue;
}
2020-05-30 04:27:59 +03:00
return tx;
});
}
function populateCallTransaction(contract, fragment, args, overrides) {
return __awaiter(this, void 0, void 0, function* () {
overrides = shallowCopy(overrides);
// If the contract was just deployed, wait until it is minded
if (contract.deployTransaction != null) {
2020-05-30 04:27:59 +03:00
let blockTag = undefined;
if (overrides.blockTag) {
blockTag = yield overrides.blockTag;
}
yield contract._deployed(blockTag);
}
2020-05-30 04:27:59 +03:00
// Resolved Overrides
delete overrides.blockTag;
const ro = yield resolveProperties(overrides);
delete overrides.from;
const tx = yield populateTransaction(contract, fragment, args, overrides);
if (ro.from) {
tx.from = this.interface.constructor.getAddress(ro.from);
}
return tx;
});
}
function buildPopulate(contract, fragment) {
const populate = (fragment.constant) ? populateCallTransaction : populateTransaction;
return function (...args) {
return __awaiter(this, void 0, void 0, function* () {
let overrides = null;
if (args.length === fragment.inputs.length + 1 && typeof (args[args.length - 1]) === "object") {
overrides = args.pop();
}
logger.checkArgumentCount(args.length, fragment.inputs.length, "passed to contract");
return populate(contract, fragment, args, overrides);
});
};
}
function buildEstimate(contract, fragment) {
const signerOrProvider = (contract.signer || contract.provider);
const populate = (fragment.constant) ? populateCallTransaction : populateTransaction;
return function (...args) {
return __awaiter(this, void 0, void 0, function* () {
let overrides = null;
if (args.length === fragment.inputs.length + 1 && typeof (args[args.length - 1]) === "object") {
overrides = args.pop();
}
logger.checkArgumentCount(args.length, fragment.inputs.length, "passed to contract");
if (!signerOrProvider) {
logger.throwError("estimate require a provider or signer", Logger.errors.UNSUPPORTED_OPERATION, { operation: "estimateGas" });
}
const tx = yield populate(contract, fragment, args, overrides);
return yield signerOrProvider.estimateGas(tx);
});
};
}
function buildCall(contract, fragment, collapseSimple) {
const signerOrProvider = (contract.signer || contract.provider);
const populate = (fragment.constant) ? populateCallTransaction : populateTransaction;
return function (...args) {
return __awaiter(this, void 0, void 0, function* () {
let overrides = null;
let blockTag = undefined;
if (args.length === fragment.inputs.length + 1 && typeof (args[args.length - 1]) === "object") {
overrides = shallowCopy(args.pop());
if (overrides.blockTag) {
blockTag = yield overrides.blockTag;
delete overrides.blockTag;
}
}
2020-05-30 04:27:59 +03:00
logger.checkArgumentCount(args.length, fragment.inputs.length, "passed to contract");
const tx = yield populate(contract, fragment, args, overrides);
const value = yield signerOrProvider.call(tx, blockTag);
try {
let result = contract.interface.decodeFunctionResult(fragment, value);
if (collapseSimple && fragment.outputs.length === 1) {
result = result[0];
}
2020-05-30 04:27:59 +03:00
return result;
}
2020-05-30 04:27:59 +03:00
catch (error) {
if (error.code === Logger.errors.CALL_EXCEPTION) {
error.address = contract.address;
error.args = args;
error.transaction = tx;
}
throw error;
}
2020-05-30 04:27:59 +03:00
});
};
}
function buildSend(contract, fragment) {
return function (...args) {
return __awaiter(this, void 0, void 0, function* () {
if (!contract.signer) {
2019-09-08 09:46:53 +03:00
logger.throwError("sending a transaction requires a signer", Logger.errors.UNSUPPORTED_OPERATION, { operation: "sendTransaction" });
}
2020-05-30 04:27:59 +03:00
// We allow CallOverrides, since the Signer can accept from
let overrides = null;
if (args.length === fragment.inputs.length + 1 && typeof (args[args.length - 1]) === "object") {
overrides = shallowCopy(args.pop());
if (overrides.blockTag != null) {
logger.throwArgumentError(`cannot override "blockTag" in transaction`, "overrides", overrides);
}
}
logger.checkArgumentCount(args.length, fragment.inputs.length, "passed to contract");
const txRequest = yield populateCallTransaction(contract, fragment, args, overrides);
const tx = yield contract.signer.sendTransaction(txRequest);
// Tweak the tw.wait so the receipt has extra properties
const wait = tx.wait.bind(tx);
tx.wait = (confirmations) => {
return wait(confirmations).then((receipt) => {
receipt.events = receipt.logs.map((log) => {
let event = deepCopy(log);
let parsed = null;
try {
parsed = contract.interface.parseLog(log);
}
catch (e) { }
// Successfully parsed the event log; include it
if (parsed) {
event.args = parsed.args;
event.decode = (data, topics) => {
return this.interface.decodeEventLog(parsed.eventFragment, data, topics);
};
2020-05-30 04:27:59 +03:00
event.event = parsed.name;
event.eventSignature = parsed.signature;
}
// Useful operations
event.removeListener = () => { return contract.provider; };
event.getBlock = () => {
return contract.provider.getBlock(receipt.blockHash);
};
event.getTransaction = () => {
return contract.provider.getTransaction(receipt.transactionHash);
};
event.getTransactionReceipt = () => {
return Promise.resolve(receipt);
};
return event;
});
2020-05-30 04:27:59 +03:00
return receipt;
});
};
return tx;
});
};
}
2020-05-30 04:27:59 +03:00
function buildDefault(contract, fragment, collapseSimple) {
if (fragment.constant) {
return buildCall(contract, fragment, collapseSimple);
}
return buildSend(contract, fragment);
}
function getEventTag(filter) {
if (filter.address && (filter.topics == null || filter.topics.length === 0)) {
return "*";
}
2020-05-30 04:27:59 +03:00
return (filter.address || "*") + "@" + (filter.topics ? filter.topics.map((topic) => {
if (Array.isArray(topic)) {
return topic.join("|");
}
return topic;
}).join(":") : "");
}
class RunningEvent {
constructor(tag, filter) {
defineReadOnly(this, "tag", tag);
defineReadOnly(this, "filter", filter);
this._listeners = [];
}
addListener(listener, once) {
this._listeners.push({ listener: listener, once: once });
}
removeListener(listener) {
let done = false;
this._listeners = this._listeners.filter((item) => {
if (done || item.listener !== listener) {
return true;
}
done = true;
return false;
});
}
removeAllListeners() {
this._listeners = [];
}
listeners() {
return this._listeners.map((i) => i.listener);
}
listenerCount() {
return this._listeners.length;
}
run(args) {
2020-01-08 03:58:04 +03:00
const listenerCount = this.listenerCount();
this._listeners = this._listeners.filter((item) => {
2020-01-08 03:58:04 +03:00
const argsCopy = args.slice();
// Call the callback in the next event loop
setTimeout(() => {
item.listener.apply(this, argsCopy);
}, 0);
// Reschedule it if it not "once"
return !(item.once);
});
return listenerCount;
}
prepareEvent(event) {
}
2020-04-17 04:59:53 +03:00
// Returns the array that will be applied to an emit
getEmit(event) {
return [event];
}
}
class ErrorRunningEvent extends RunningEvent {
constructor() {
super("error", null);
}
}
2020-04-17 04:59:53 +03:00
// @TODO Fragment should inherit Wildcard? and just override getEmit?
// or have a common abstract super class, with enough constructor
// options to configure both.
// A Fragment Event will populate all the properties that Wildcard
// will, and additioanlly dereference the arguments when emitting
class FragmentRunningEvent extends RunningEvent {
constructor(address, contractInterface, fragment, topics) {
2020-01-08 03:58:04 +03:00
const filter = {
address: address
};
let topic = contractInterface.getEventTopic(fragment);
if (topics) {
if (topic !== topics[0]) {
logger.throwArgumentError("topic mismatch", "topics", topics);
}
filter.topics = topics.slice();
}
else {
filter.topics = [topic];
}
super(getEventTag(filter), filter);
defineReadOnly(this, "address", address);
defineReadOnly(this, "interface", contractInterface);
defineReadOnly(this, "fragment", fragment);
}
prepareEvent(event) {
super.prepareEvent(event);
event.event = this.fragment.name;
event.eventSignature = this.fragment.format();
event.decode = (data, topics) => {
return this.interface.decodeEventLog(this.fragment, data, topics);
};
2020-04-17 04:59:53 +03:00
try {
event.args = this.interface.decodeEventLog(this.fragment, event.data, event.topics);
}
catch (error) {
event.args = null;
event.decodeError = error;
}
}
getEmit(event) {
2020-04-25 10:54:54 +03:00
const errors = checkResultErrors(event.args);
if (errors.length) {
throw errors[0].error;
}
2020-04-17 04:59:53 +03:00
const args = (event.args || []).slice();
args.push(event);
return args;
}
}
2020-04-17 04:59:53 +03:00
// A Wildard Event will attempt to populate:
// - event The name of the event name
// - eventSignature The full signature of the event
// - decode A function to decode data and topics
// - args The decoded data and topics
class WildcardRunningEvent extends RunningEvent {
constructor(address, contractInterface) {
super("*", { address: address });
defineReadOnly(this, "address", address);
defineReadOnly(this, "interface", contractInterface);
}
prepareEvent(event) {
super.prepareEvent(event);
2020-04-17 04:59:53 +03:00
try {
const parsed = this.interface.parseLog(event);
event.event = parsed.name;
event.eventSignature = parsed.signature;
event.decode = (data, topics) => {
return this.interface.decodeEventLog(parsed.eventFragment, data, topics);
};
2020-01-08 03:58:04 +03:00
event.args = parsed.args;
}
2020-04-17 04:59:53 +03:00
catch (error) {
// No matching event
}
}
}
export class Contract {
constructor(addressOrName, contractInterface, signerOrProvider) {
logger.checkNew(new.target, Contract);
// @TODO: Maybe still check the addressOrName looks like a valid address or name?
//address = getAddress(address);
defineReadOnly(this, "interface", getStatic((new.target), "getInterface")(contractInterface));
if (Signer.isSigner(signerOrProvider)) {
defineReadOnly(this, "provider", signerOrProvider.provider || null);
defineReadOnly(this, "signer", signerOrProvider);
}
else if (Provider.isProvider(signerOrProvider)) {
defineReadOnly(this, "provider", signerOrProvider);
defineReadOnly(this, "signer", null);
}
else {
logger.throwArgumentError("invalid signer or provider", "signerOrProvider", signerOrProvider);
}
defineReadOnly(this, "callStatic", {});
2020-03-12 21:14:50 +03:00
defineReadOnly(this, "estimateGas", {});
defineReadOnly(this, "functions", {});
defineReadOnly(this, "populateTransaction", {});
defineReadOnly(this, "filters", {});
2019-11-20 12:57:38 +03:00
{
const uniqueFilters = {};
Object.keys(this.interface.events).forEach((eventSignature) => {
2020-01-08 03:58:04 +03:00
const event = this.interface.events[eventSignature];
2019-11-20 12:57:38 +03:00
defineReadOnly(this.filters, eventSignature, (...args) => {
return {
address: this.address,
topics: this.interface.encodeFilterTopics(event, args)
};
});
if (!uniqueFilters[event.name]) {
uniqueFilters[event.name] = [];
}
uniqueFilters[event.name].push(eventSignature);
});
2019-11-20 12:57:38 +03:00
Object.keys(uniqueFilters).forEach((name) => {
const filters = uniqueFilters[name];
if (filters.length === 1) {
defineReadOnly(this.filters, name, this.filters[filters[0]]);
}
else {
logger.warn(`Duplicate definition of ${name} (${filters.join(", ")})`);
}
});
}
defineReadOnly(this, "_runningEvents", {});
defineReadOnly(this, "_wrappedEmits", {});
defineReadOnly(this, "address", addressOrName);
if (this.provider) {
2020-03-12 21:14:50 +03:00
defineReadOnly(this, "resolvedAddress", this.provider.resolveName(addressOrName).then((address) => {
if (address == null) {
throw new Error("name not found");
}
return address;
}).catch((error) => {
console.log("ERROR: Cannot find Contract - " + addressOrName);
throw error;
}));
}
else {
try {
2020-03-12 21:14:50 +03:00
defineReadOnly(this, "resolvedAddress", Promise.resolve((this.interface.constructor).getAddress(addressOrName)));
}
catch (error) {
// Without a provider, we cannot use ENS names
logger.throwArgumentError("provider is required to use non-address contract address", "addressOrName", addressOrName);
}
}
2020-05-04 00:53:58 +03:00
const uniqueNames = {};
const uniqueSignatures = {};
Object.keys(this.interface.functions).forEach((signature) => {
const fragment = this.interface.functions[signature];
// Check that the signature is unique; if not the ABI generation has
// not been cleaned or may be incorrectly generated
if (uniqueSignatures[signature]) {
logger.warn(`Duplicate ABI entry for ${JSON.stringify(name)}`);
return;
}
2020-05-04 00:53:58 +03:00
uniqueSignatures[signature] = true;
// Track unique names; we only expose bare named functions if they
// are ambiguous
{
const name = fragment.name;
if (!uniqueNames[name]) {
uniqueNames[name] = [];
}
uniqueNames[name].push(signature);
}
2020-05-04 00:53:58 +03:00
if (this[signature] == null) {
2020-05-30 04:27:59 +03:00
defineReadOnly(this, signature, buildDefault(this, fragment, true));
}
2020-05-30 04:27:59 +03:00
// We do not collapse simple calls on this bucket, which allows
// frameworks to safely use this without introspection as well as
// allows decoding error recovery.
2020-05-04 00:53:58 +03:00
if (this.functions[signature] == null) {
2020-05-30 04:27:59 +03:00
defineReadOnly(this.functions, signature, buildDefault(this, fragment, false));
}
2020-05-04 00:53:58 +03:00
if (this.callStatic[signature] == null) {
2020-05-30 04:27:59 +03:00
defineReadOnly(this.callStatic, signature, buildCall(this, fragment, true));
}
2020-05-04 00:53:58 +03:00
if (this.populateTransaction[signature] == null) {
2020-05-30 04:27:59 +03:00
defineReadOnly(this.populateTransaction, signature, buildPopulate(this, fragment));
2020-05-04 00:53:58 +03:00
}
if (this.estimateGas[signature] == null) {
2020-05-30 04:27:59 +03:00
defineReadOnly(this.estimateGas, signature, buildEstimate(this, fragment));
2019-11-20 12:57:38 +03:00
}
});
2020-05-04 00:53:58 +03:00
Object.keys(uniqueNames).forEach((name) => {
// Ambiguous names to not get attached as bare names
const signatures = uniqueNames[name];
2019-11-20 12:57:38 +03:00
if (signatures.length > 1) {
return;
}
2020-05-04 00:53:58 +03:00
const signature = signatures[0];
2019-11-20 12:57:38 +03:00
if (this[name] == null) {
2020-05-04 00:53:58 +03:00
defineReadOnly(this, name, this[signature]);
}
if (this.functions[name] == null) {
defineReadOnly(this.functions, name, this.functions[signature]);
}
if (this.callStatic[name] == null) {
defineReadOnly(this.callStatic, name, this.callStatic[signature]);
}
if (this.populateTransaction[name] == null) {
defineReadOnly(this.populateTransaction, name, this.populateTransaction[signature]);
}
if (this.estimateGas[name] == null) {
defineReadOnly(this.estimateGas, name, this.estimateGas[signature]);
2019-11-20 12:57:38 +03:00
}
});
}
static getContractAddress(transaction) {
return getContractAddress(transaction);
}
static getInterface(contractInterface) {
if (Interface.isInterface(contractInterface)) {
return contractInterface;
}
return new Interface(contractInterface);
}
// @TODO: Allow timeout?
deployed() {
return this._deployed();
}
_deployed(blockTag) {
if (!this._deployedPromise) {
// If we were just deployed, we know the transaction we should occur in
if (this.deployTransaction) {
this._deployedPromise = this.deployTransaction.wait().then(() => {
return this;
});
}
else {
// @TODO: Once we allow a timeout to be passed in, we will wait
// up to that many blocks for getCode
// Otherwise, poll for our code to be deployed
this._deployedPromise = this.provider.getCode(this.address, blockTag).then((code) => {
if (code === "0x") {
logger.throwError("contract not deployed", Logger.errors.UNSUPPORTED_OPERATION, {
contractAddress: this.address,
operation: "getDeployed"
});
}
return this;
});
}
}
return this._deployedPromise;
}
// @TODO:
// estimateFallback(overrides?: TransactionRequest): Promise<BigNumber>
// @TODO:
// estimateDeploy(bytecode: string, ...args): Promise<BigNumber>
fallback(overrides) {
if (!this.signer) {
2019-09-08 09:46:53 +03:00
logger.throwError("sending a transactions require a signer", Logger.errors.UNSUPPORTED_OPERATION, { operation: "sendTransaction(fallback)" });
}
2020-01-08 03:58:04 +03:00
const tx = shallowCopy(overrides || {});
["from", "to"].forEach(function (key) {
if (tx[key] == null) {
return;
}
logger.throwError("cannot override " + key, Logger.errors.UNSUPPORTED_OPERATION, { operation: key });
});
2020-03-12 21:14:50 +03:00
tx.to = this.resolvedAddress;
return this.deployed().then(() => {
return this.signer.sendTransaction(tx);
});
}
// Reconnect to a different signer or provider
connect(signerOrProvider) {
if (typeof (signerOrProvider) === "string") {
signerOrProvider = new VoidSigner(signerOrProvider, this.provider);
}
2020-01-08 03:58:04 +03:00
const contract = new (this.constructor)(this.address, this.interface, signerOrProvider);
if (this.deployTransaction) {
defineReadOnly(contract, "deployTransaction", this.deployTransaction);
}
return contract;
}
// Re-attach to a different on-chain instance of this contract
attach(addressOrName) {
return new (this.constructor)(addressOrName, this.interface, this.signer || this.provider);
}
static isIndexed(value) {
return Indexed.isIndexed(value);
}
_normalizeRunningEvent(runningEvent) {
// Already have an instance of this event running; we can re-use it
if (this._runningEvents[runningEvent.tag]) {
return this._runningEvents[runningEvent.tag];
}
return runningEvent;
}
_getRunningEvent(eventName) {
if (typeof (eventName) === "string") {
// Listen for "error" events (if your contract has an error event, include
// the full signature to bypass this special event keyword)
if (eventName === "error") {
return this._normalizeRunningEvent(new ErrorRunningEvent());
}
2020-04-25 10:54:54 +03:00
// Listen for any event that is registered
if (eventName === "event") {
return this._normalizeRunningEvent(new RunningEvent("event", null));
}
// Listen for any event
if (eventName === "*") {
return this._normalizeRunningEvent(new WildcardRunningEvent(this.address, this.interface));
}
2020-04-17 04:59:53 +03:00
// Get the event Fragment (throws if ambiguous/unknown event)
2020-01-08 03:58:04 +03:00
const fragment = this.interface.getEvent(eventName);
return this._normalizeRunningEvent(new FragmentRunningEvent(this.address, this.interface, fragment));
}
2020-04-17 04:59:53 +03:00
// We have topics to filter by...
if (eventName.topics && eventName.topics.length > 0) {
// Is it a known topichash? (throws if no matching topichash)
try {
2020-05-30 04:27:59 +03:00
const topic = eventName.topics[0];
if (typeof (topic) !== "string") {
throw new Error("invalid topic"); // @TODO: May happen for anonymous events
}
const fragment = this.interface.getEvent(topic);
2020-04-17 04:59:53 +03:00
return this._normalizeRunningEvent(new FragmentRunningEvent(this.address, this.interface, fragment, eventName.topics));
}
2020-04-17 04:59:53 +03:00
catch (error) { }
// Filter by the unknown topichash
const filter = {
address: this.address,
topics: eventName.topics
};
return this._normalizeRunningEvent(new RunningEvent(getEventTag(filter), filter));
}
2020-04-17 04:59:53 +03:00
return this._normalizeRunningEvent(new WildcardRunningEvent(this.address, this.interface));
}
_checkRunningEvents(runningEvent) {
if (runningEvent.listenerCount() === 0) {
delete this._runningEvents[runningEvent.tag];
2020-04-16 01:28:04 +03:00
// If we have a poller for this, remove it
const emit = this._wrappedEmits[runningEvent.tag];
if (emit) {
this.provider.off(runningEvent.filter, emit);
delete this._wrappedEmits[runningEvent.tag];
}
}
}
2020-04-17 04:59:53 +03:00
// Subclasses can override this to gracefully recover
// from parse errors if they wish
_wrapEvent(runningEvent, log, listener) {
2020-01-08 03:58:04 +03:00
const event = deepCopy(log);
event.removeListener = () => {
if (!listener) {
return;
}
runningEvent.removeListener(listener);
this._checkRunningEvents(runningEvent);
};
event.getBlock = () => { return this.provider.getBlock(log.blockHash); };
event.getTransaction = () => { return this.provider.getTransaction(log.transactionHash); };
event.getTransactionReceipt = () => { return this.provider.getTransactionReceipt(log.transactionHash); };
2020-04-17 04:59:53 +03:00
// This may throw if the topics and data mismatch the signature
runningEvent.prepareEvent(event);
return event;
}
_addEventListener(runningEvent, listener, once) {
if (!this.provider) {
logger.throwError("events require a provider or a signer with a provider", Logger.errors.UNSUPPORTED_OPERATION, { operation: "once" });
}
runningEvent.addListener(listener, once);
// Track this running event and its listeners (may already be there; but no hard in updating)
this._runningEvents[runningEvent.tag] = runningEvent;
2020-04-17 04:59:53 +03:00
// If we are not polling the provider, start polling
if (!this._wrappedEmits[runningEvent.tag]) {
2020-01-08 03:58:04 +03:00
const wrappedEmit = (log) => {
2020-04-25 10:54:54 +03:00
let event = this._wrapEvent(runningEvent, log, listener);
// Try to emit the result for the parameterized event...
if (event.decodeError == null) {
try {
const args = runningEvent.getEmit(event);
this.emit(runningEvent.filter, ...args);
}
catch (error) {
event.decodeError = error.error;
}
}
// Always emit "event" for fragment-base events
if (runningEvent.filter != null) {
this.emit("event", event);
2020-04-17 04:59:53 +03:00
}
2020-04-25 10:54:54 +03:00
// Emit "error" if there was an error
if (event.decodeError != null) {
this.emit("error", event.decodeError, event);
2020-04-17 04:59:53 +03:00
}
};
this._wrappedEmits[runningEvent.tag] = wrappedEmit;
// Special events, like "error" do not have a filter
if (runningEvent.filter != null) {
this.provider.on(runningEvent.filter, wrappedEmit);
}
}
}
queryFilter(event, fromBlockOrBlockhash, toBlock) {
2020-01-08 03:58:04 +03:00
const runningEvent = this._getRunningEvent(event);
const filter = shallowCopy(runningEvent.filter);
if (typeof (fromBlockOrBlockhash) === "string" && isHexString(fromBlockOrBlockhash, 32)) {
if (toBlock != null) {
logger.throwArgumentError("cannot specify toBlock with blockhash", "toBlock", toBlock);
}
2020-05-21 07:07:41 +03:00
filter.blockHash = fromBlockOrBlockhash;
}
else {
filter.fromBlock = ((fromBlockOrBlockhash != null) ? fromBlockOrBlockhash : 0);
filter.toBlock = ((toBlock != null) ? toBlock : "latest");
}
return this.provider.getLogs(filter).then((logs) => {
return logs.map((log) => this._wrapEvent(runningEvent, log, null));
});
}
on(event, listener) {
this._addEventListener(this._getRunningEvent(event), listener, false);
return this;
}
once(event, listener) {
this._addEventListener(this._getRunningEvent(event), listener, true);
return this;
}
emit(eventName, ...args) {
if (!this.provider) {
return false;
}
2020-01-08 03:58:04 +03:00
const runningEvent = this._getRunningEvent(eventName);
const result = (runningEvent.run(args) > 0);
// May have drained all the "once" events; check for living events
this._checkRunningEvents(runningEvent);
return result;
}
listenerCount(eventName) {
if (!this.provider) {
return 0;
}
return this._getRunningEvent(eventName).listenerCount();
}
listeners(eventName) {
if (!this.provider) {
return [];
}
if (eventName == null) {
2020-01-08 03:58:04 +03:00
const result = [];
for (let tag in this._runningEvents) {
this._runningEvents[tag].listeners().forEach((listener) => {
result.push(listener);
});
}
return result;
}
return this._getRunningEvent(eventName).listeners();
}
removeAllListeners(eventName) {
if (!this.provider) {
return this;
}
if (eventName == null) {
2020-01-08 03:58:04 +03:00
for (const tag in this._runningEvents) {
const runningEvent = this._runningEvents[tag];
runningEvent.removeAllListeners();
this._checkRunningEvents(runningEvent);
}
return this;
}
// Delete any listeners
2020-01-08 03:58:04 +03:00
const runningEvent = this._getRunningEvent(eventName);
runningEvent.removeAllListeners();
this._checkRunningEvents(runningEvent);
return this;
}
off(eventName, listener) {
if (!this.provider) {
return this;
}
2020-01-08 03:58:04 +03:00
const runningEvent = this._getRunningEvent(eventName);
runningEvent.removeListener(listener);
this._checkRunningEvents(runningEvent);
return this;
}
removeListener(eventName, listener) {
return this.off(eventName, listener);
}
}
export class ContractFactory {
constructor(contractInterface, bytecode, signer) {
let bytecodeHex = null;
if (typeof (bytecode) === "string") {
bytecodeHex = bytecode;
}
else if (isBytes(bytecode)) {
bytecodeHex = hexlify(bytecode);
}
else if (bytecode && typeof (bytecode.object) === "string") {
// Allow the bytecode object from the Solidity compiler
bytecodeHex = bytecode.object;
}
else {
// Crash in the next verification step
bytecodeHex = "!";
}
// Make sure it is 0x prefixed
if (bytecodeHex.substring(0, 2) !== "0x") {
bytecodeHex = "0x" + bytecodeHex;
}
// Make sure the final result is valid bytecode
if (!isHexString(bytecodeHex) || (bytecodeHex.length % 2)) {
logger.throwArgumentError("invalid bytecode", "bytecode", bytecode);
}
// If we have a signer, make sure it is valid
if (signer && !Signer.isSigner(signer)) {
logger.throwArgumentError("invalid signer", "signer", signer);
}
defineReadOnly(this, "bytecode", bytecodeHex);
defineReadOnly(this, "interface", getStatic((new.target), "getInterface")(contractInterface));
defineReadOnly(this, "signer", signer || null);
}
2020-05-30 04:27:59 +03:00
// @TODO: Future; rename to populteTransaction?
getDeployTransaction(...args) {
let tx = {};
// If we have 1 additional argument, we allow transaction overrides
2020-04-24 06:35:39 +03:00
if (args.length === this.interface.deploy.inputs.length + 1 && typeof (args[args.length - 1]) === "object") {
tx = shallowCopy(args.pop());
2020-01-08 03:58:04 +03:00
for (const key in tx) {
if (!allowedTransactionKeys[key]) {
throw new Error("unknown transaction override " + key);
}
}
}
// Do not allow these to be overridden in a deployment transaction
["data", "from", "to"].forEach((key) => {
if (tx[key] == null) {
return;
}
logger.throwError("cannot override " + key, Logger.errors.UNSUPPORTED_OPERATION, { operation: key });
});
// Make sure the call matches the constructor signature
logger.checkArgumentCount(args.length, this.interface.deploy.inputs.length, " in Contract constructor");
// Set the data to the bytecode + the encoded constructor arguments
tx.data = hexlify(concat([
this.bytecode,
this.interface.encodeDeploy(args)
]));
return tx;
}
deploy(...args) {
2020-04-24 06:35:39 +03:00
return __awaiter(this, void 0, void 0, function* () {
let overrides = {};
// If 1 extra parameter was passed in, it contains overrides
if (args.length === this.interface.deploy.inputs.length + 1) {
overrides = args.pop();
}
// Make sure the call matches the constructor signature
logger.checkArgumentCount(args.length, this.interface.deploy.inputs.length, " in Contract constructor");
// Resolve ENS names and promises in the arguments
const params = yield resolveAddresses(this.signer, args, this.interface.deploy.inputs);
params.push(overrides);
// Get the deployment transaction (with optional overrides)
2020-04-24 06:35:39 +03:00
const unsignedTx = this.getDeployTransaction(...params);
// Send the deployment transaction
2020-04-24 06:35:39 +03:00
const tx = yield this.signer.sendTransaction(unsignedTx);
const address = getStatic(this.constructor, "getContractAddress")(tx);
const contract = getStatic(this.constructor, "getContract")(address, this.interface, this.signer);
defineReadOnly(contract, "deployTransaction", tx);
return contract;
});
}
attach(address) {
return (this.constructor).getContract(address, this.interface, this.signer);
}
connect(signer) {
return new (this.constructor)(this.interface, this.bytecode, signer);
}
static fromSolidity(compilerOutput, signer) {
if (compilerOutput == null) {
logger.throwError("missing compiler output", Logger.errors.MISSING_ARGUMENT, { argument: "compilerOutput" });
}
if (typeof (compilerOutput) === "string") {
compilerOutput = JSON.parse(compilerOutput);
}
2020-01-08 03:58:04 +03:00
const abi = compilerOutput.abi;
let bytecode = null;
if (compilerOutput.bytecode) {
bytecode = compilerOutput.bytecode;
}
else if (compilerOutput.evm && compilerOutput.evm.bytecode) {
bytecode = compilerOutput.evm.bytecode;
}
return new this(abi, bytecode, signer);
}
static getInterface(contractInterface) {
return Contract.getInterface(contractInterface);
}
static getContractAddress(tx) {
return getContractAddress(tx);
}
static getContract(address, contractInterface, signer) {
return new Contract(address, contractInterface, signer);
}
}