parent
7f5035bb05
commit
42dee67187
@ -3,7 +3,7 @@
|
|||||||
import { BigNumber, BigNumberish } from "@ethersproject/bignumber";
|
import { BigNumber, BigNumberish } from "@ethersproject/bignumber";
|
||||||
import { BytesLike, isHexString } from "@ethersproject/bytes";
|
import { BytesLike, isHexString } from "@ethersproject/bytes";
|
||||||
import { Network } from "@ethersproject/networks";
|
import { Network } from "@ethersproject/networks";
|
||||||
import { Description, defineReadOnly } from "@ethersproject/properties";
|
import { Deferrable, Description, defineReadOnly } from "@ethersproject/properties";
|
||||||
import { Transaction } from "@ethersproject/transactions";
|
import { Transaction } from "@ethersproject/transactions";
|
||||||
import { OnceBlockable } from "@ethersproject/web";
|
import { OnceBlockable } from "@ethersproject/web";
|
||||||
|
|
||||||
@ -16,16 +16,16 @@ const logger = new Logger(version);
|
|||||||
|
|
||||||
|
|
||||||
export type TransactionRequest = {
|
export type TransactionRequest = {
|
||||||
to?: string | Promise<string>,
|
to?: string,
|
||||||
from?: string | Promise<string>,
|
from?: string,
|
||||||
nonce?: BigNumberish | Promise<BigNumberish>,
|
nonce?: BigNumberish,
|
||||||
|
|
||||||
gasLimit?: BigNumberish | Promise<BigNumberish>,
|
gasLimit?: BigNumberish,
|
||||||
gasPrice?: BigNumberish | Promise<BigNumberish>,
|
gasPrice?: BigNumberish,
|
||||||
|
|
||||||
data?: BytesLike | Promise<BytesLike>,
|
data?: BytesLike,
|
||||||
value?: BigNumberish | Promise<BigNumberish>,
|
value?: BigNumberish,
|
||||||
chainId?: number | Promise<number>,
|
chainId?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TransactionResponse extends Transaction {
|
export interface TransactionResponse extends Transaction {
|
||||||
@ -221,8 +221,8 @@ export abstract class Provider implements OnceBlockable {
|
|||||||
|
|
||||||
// Execution
|
// Execution
|
||||||
abstract sendTransaction(signedTransaction: string | Promise<string>): Promise<TransactionResponse>;
|
abstract sendTransaction(signedTransaction: string | Promise<string>): Promise<TransactionResponse>;
|
||||||
abstract call(transaction: TransactionRequest, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
|
abstract call(transaction: Deferrable<TransactionRequest>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
|
||||||
abstract estimateGas(transaction: TransactionRequest): Promise<BigNumber>;
|
abstract estimateGas(transaction: Deferrable<TransactionRequest>): Promise<BigNumber>;
|
||||||
|
|
||||||
// Queries
|
// Queries
|
||||||
abstract getBlock(blockHashOrBlockTag: BlockTag | string | Promise<BlockTag | string>): Promise<Block>;
|
abstract getBlock(blockHashOrBlockTag: BlockTag | string | Promise<BlockTag | string>): Promise<Block>;
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import { BlockTag, Provider, TransactionRequest, TransactionResponse } from "@ethersproject/abstract-provider";
|
import { BlockTag, Provider, TransactionRequest, TransactionResponse } from "@ethersproject/abstract-provider";
|
||||||
import { BigNumber } from "@ethersproject/bignumber";
|
import { BigNumber } from "@ethersproject/bignumber";
|
||||||
import { Bytes } from "@ethersproject/bytes";
|
import { Bytes } from "@ethersproject/bytes";
|
||||||
import { defineReadOnly, resolveProperties, shallowCopy } from "@ethersproject/properties";
|
import { Deferrable, defineReadOnly, resolveProperties, shallowCopy } from "@ethersproject/properties";
|
||||||
|
|
||||||
import { Logger } from "@ethersproject/logger";
|
import { Logger } from "@ethersproject/logger";
|
||||||
import { version } from "./_version";
|
import { version } from "./_version";
|
||||||
@ -46,7 +46,7 @@ export abstract class Signer {
|
|||||||
// The EXACT transaction MUST be signed, and NO additional properties to be added.
|
// The EXACT transaction MUST be signed, and NO additional properties to be added.
|
||||||
// - This MAY throw if signing transactions is not supports, but if
|
// - This MAY throw if signing transactions is not supports, but if
|
||||||
// it does, sentTransaction MUST be overridden.
|
// it does, sentTransaction MUST be overridden.
|
||||||
abstract signTransaction(transaction: TransactionRequest): Promise<string>;
|
abstract signTransaction(transaction: Deferrable<TransactionRequest>): Promise<string>;
|
||||||
|
|
||||||
// Returns a new instance of the Signer, connected to provider.
|
// Returns a new instance of the Signer, connected to provider.
|
||||||
// This MAY throw if changing providers is not supported.
|
// This MAY throw if changing providers is not supported.
|
||||||
@ -77,21 +77,21 @@ export abstract class Signer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Populates "from" if unspecified, and estimates the gas for the transation
|
// Populates "from" if unspecified, and estimates the gas for the transation
|
||||||
async estimateGas(transaction: TransactionRequest): Promise<BigNumber> {
|
async estimateGas(transaction: Deferrable<TransactionRequest>): Promise<BigNumber> {
|
||||||
this._checkProvider("estimateGas");
|
this._checkProvider("estimateGas");
|
||||||
const tx = await resolveProperties(this.checkTransaction(transaction));
|
const tx = await resolveProperties(this.checkTransaction(transaction));
|
||||||
return await this.provider.estimateGas(tx);
|
return await this.provider.estimateGas(tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populates "from" if unspecified, and calls with the transation
|
// Populates "from" if unspecified, and calls with the transation
|
||||||
async call(transaction: TransactionRequest, blockTag?: BlockTag): Promise<string> {
|
async call(transaction: Deferrable<TransactionRequest>, blockTag?: BlockTag): Promise<string> {
|
||||||
this._checkProvider("call");
|
this._checkProvider("call");
|
||||||
const tx = await resolveProperties(this.checkTransaction(transaction));
|
const tx = await resolveProperties(this.checkTransaction(transaction));
|
||||||
return await this.provider.call(tx, blockTag);
|
return await this.provider.call(tx, blockTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populates all fields in a transaction, signs it and sends it to the network
|
// Populates all fields in a transaction, signs it and sends it to the network
|
||||||
sendTransaction(transaction: TransactionRequest): Promise<TransactionResponse> {
|
sendTransaction(transaction: Deferrable<TransactionRequest>): Promise<TransactionResponse> {
|
||||||
this._checkProvider("sendTransaction");
|
this._checkProvider("sendTransaction");
|
||||||
return this.populateTransaction(transaction).then((tx) => {
|
return this.populateTransaction(transaction).then((tx) => {
|
||||||
return this.signTransaction(tx).then((signedTx) => {
|
return this.signTransaction(tx).then((signedTx) => {
|
||||||
@ -128,7 +128,7 @@ export abstract class Signer {
|
|||||||
// - call
|
// - call
|
||||||
// - estimateGas
|
// - estimateGas
|
||||||
// - populateTransaction (and therefor sendTransaction)
|
// - populateTransaction (and therefor sendTransaction)
|
||||||
checkTransaction(transaction: TransactionRequest): TransactionRequest {
|
checkTransaction(transaction: Deferrable<TransactionRequest>): Deferrable<TransactionRequest> {
|
||||||
for (const key in transaction) {
|
for (const key in transaction) {
|
||||||
if (allowedTransactionKeys.indexOf(key) === -1) {
|
if (allowedTransactionKeys.indexOf(key) === -1) {
|
||||||
logger.throwArgumentError("invalid transaction key: " + key, "transaction", transaction);
|
logger.throwArgumentError("invalid transaction key: " + key, "transaction", transaction);
|
||||||
@ -159,9 +159,9 @@ export abstract class Signer {
|
|||||||
// this Signer. Should be used by sendTransaction but NOT by signTransaction.
|
// this Signer. Should be used by sendTransaction but NOT by signTransaction.
|
||||||
// By default called from: (overriding these prevents it)
|
// By default called from: (overriding these prevents it)
|
||||||
// - sendTransaction
|
// - sendTransaction
|
||||||
async populateTransaction(transaction: TransactionRequest): Promise<TransactionRequest> {
|
async populateTransaction(transaction: Deferrable<TransactionRequest>): Promise<TransactionRequest> {
|
||||||
|
|
||||||
const tx: TransactionRequest = await resolveProperties(this.checkTransaction(transaction))
|
const tx: Deferrable<TransactionRequest> = await resolveProperties(this.checkTransaction(transaction))
|
||||||
|
|
||||||
if (tx.to != null) { tx.to = Promise.resolve(tx.to).then((to) => this.resolveName(to)); }
|
if (tx.to != null) { tx.to = Promise.resolve(tx.to).then((to) => this.resolveName(to)); }
|
||||||
if (tx.gasPrice == null) { tx.gasPrice = this.getGasPrice(); }
|
if (tx.gasPrice == null) { tx.gasPrice = this.getGasPrice(); }
|
||||||
@ -232,7 +232,7 @@ export class VoidSigner extends Signer {
|
|||||||
return this._fail("VoidSigner cannot sign messages", "signMessage");
|
return this._fail("VoidSigner cannot sign messages", "signMessage");
|
||||||
}
|
}
|
||||||
|
|
||||||
signTransaction(transaction: TransactionRequest): Promise<string> {
|
signTransaction(transaction: Deferrable<TransactionRequest>): Promise<string> {
|
||||||
return this._fail("VoidSigner cannot sign transactions", "signTransaction");
|
return this._fail("VoidSigner cannot sign transactions", "signTransaction");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
import { checkResultErrors, EventFragment, Fragment, FunctionFragment, Indexed, Interface, JsonFragment, LogDescription, ParamType, Result } from "@ethersproject/abi";
|
import { checkResultErrors, EventFragment, Fragment, FunctionFragment, Indexed, Interface, JsonFragment, LogDescription, ParamType, Result } from "@ethersproject/abi";
|
||||||
import { Block, BlockTag, Filter, FilterByBlockHash, Listener, Log, Provider, TransactionReceipt, TransactionRequest, TransactionResponse } from "@ethersproject/abstract-provider";
|
import { Block, BlockTag, Filter, FilterByBlockHash, Listener, Log, Provider, TransactionReceipt, TransactionRequest, TransactionResponse } from "@ethersproject/abstract-provider";
|
||||||
import { Signer, VoidSigner } from "@ethersproject/abstract-signer";
|
import { Signer, VoidSigner } from "@ethersproject/abstract-signer";
|
||||||
import { getContractAddress } from "@ethersproject/address";
|
import { getAddress, getContractAddress } from "@ethersproject/address";
|
||||||
import { BigNumber, BigNumberish } from "@ethersproject/bignumber";
|
import { BigNumber, BigNumberish } from "@ethersproject/bignumber";
|
||||||
import { BytesLike, concat, hexlify, isBytes, isHexString } from "@ethersproject/bytes";
|
import { BytesLike, concat, hexlify, isBytes, isHexString } from "@ethersproject/bytes";
|
||||||
import { defineReadOnly, deepCopy, getStatic, resolveProperties, shallowCopy } from "@ethersproject/properties";
|
//import { AddressZero } from "@ethersproject/constants";
|
||||||
import { UnsignedTransaction } from "@ethersproject/transactions";
|
import { Deferrable, defineReadOnly, deepCopy, getStatic, resolveProperties, shallowCopy } from "@ethersproject/properties";
|
||||||
|
// @TOOD remove dependences transactions
|
||||||
|
|
||||||
import { Logger } from "@ethersproject/logger";
|
import { Logger } from "@ethersproject/logger";
|
||||||
import { version } from "./_version";
|
import { version } from "./_version";
|
||||||
@ -18,7 +19,7 @@ export interface Overrides {
|
|||||||
gasLimit?: BigNumberish | Promise<BigNumberish>;
|
gasLimit?: BigNumberish | Promise<BigNumberish>;
|
||||||
gasPrice?: BigNumberish | Promise<BigNumberish>;
|
gasPrice?: BigNumberish | Promise<BigNumberish>;
|
||||||
nonce?: BigNumberish | Promise<BigNumberish>;
|
nonce?: BigNumberish | Promise<BigNumberish>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface PayableOverrides extends Overrides {
|
export interface PayableOverrides extends Overrides {
|
||||||
value?: BigNumberish | Promise<BigNumberish>;
|
value?: BigNumberish | Promise<BigNumberish>;
|
||||||
@ -26,9 +27,26 @@ export interface PayableOverrides extends Overrides {
|
|||||||
|
|
||||||
export interface CallOverrides extends PayableOverrides {
|
export interface CallOverrides extends PayableOverrides {
|
||||||
blockTag?: BlockTag | Promise<BlockTag>;
|
blockTag?: BlockTag | Promise<BlockTag>;
|
||||||
from?: string | Promise<string>
|
from?: string | Promise<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @TODO: Better hierarchy with: (in v6)
|
||||||
|
// - abstract-provider:TransactionRequest
|
||||||
|
// - transactions:Transaction
|
||||||
|
// - transaction:UnsignedTransaction
|
||||||
|
|
||||||
|
export interface PopulatedTransaction {
|
||||||
|
to?: string;
|
||||||
|
from?: string;
|
||||||
|
nonce?: number;
|
||||||
|
|
||||||
|
gasLimit?: BigNumber;
|
||||||
|
gasPrice?: BigNumber;
|
||||||
|
|
||||||
|
data?: string;
|
||||||
|
value?: BigNumber;
|
||||||
|
chainId?: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type EventFilter = {
|
export type EventFilter = {
|
||||||
address?: string;
|
address?: string;
|
||||||
@ -80,13 +98,29 @@ const allowedTransactionKeys: { [ key: string ]: boolean } = {
|
|||||||
chainId: true, data: true, from: true, gasLimit: true, gasPrice:true, nonce: true, to: true, value: true
|
chainId: true, data: true, from: true, gasLimit: true, gasPrice:true, nonce: true, to: true, value: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function resolveName(resolver: Signer | Provider, nameOrPromise: string | Promise<string>): Promise<string> {
|
||||||
|
const name = await nameOrPromise;
|
||||||
|
|
||||||
|
// If it is already an address, just use it (after adding checksum)
|
||||||
|
try {
|
||||||
|
return getAddress(name);
|
||||||
|
} catch (error) { }
|
||||||
|
|
||||||
|
if (!resolver) {
|
||||||
|
logger.throwError("a provider or signer is needed to resolve ENS names", Logger.errors.UNSUPPORTED_OPERATION, {
|
||||||
|
operation: "resolveName"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return await resolver.resolveName(name);
|
||||||
|
}
|
||||||
|
|
||||||
// Recursively replaces ENS names with promises to resolve the name and resolves all properties
|
// Recursively replaces ENS names with promises to resolve the name and resolves all properties
|
||||||
function resolveAddresses(signerOrProvider: Signer | Provider, value: any, paramType: ParamType | Array<ParamType>): Promise<any> {
|
function resolveAddresses(resolver: Signer | Provider, value: any, paramType: ParamType | Array<ParamType>): Promise<any> {
|
||||||
if (Array.isArray(paramType)) {
|
if (Array.isArray(paramType)) {
|
||||||
return Promise.all(paramType.map((paramType, index) => {
|
return Promise.all(paramType.map((paramType, index) => {
|
||||||
return resolveAddresses(
|
return resolveAddresses(
|
||||||
signerOrProvider,
|
resolver,
|
||||||
((Array.isArray(value)) ? value[index]: value[paramType.name]),
|
((Array.isArray(value)) ? value[index]: value[paramType.name]),
|
||||||
paramType
|
paramType
|
||||||
);
|
);
|
||||||
@ -94,25 +128,62 @@ function resolveAddresses(signerOrProvider: Signer | Provider, value: any, param
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (paramType.type === "address") {
|
if (paramType.type === "address") {
|
||||||
return signerOrProvider.resolveName(value);
|
return resolveName(resolver, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (paramType.type === "tuple") {
|
if (paramType.type === "tuple") {
|
||||||
return resolveAddresses(signerOrProvider, value, paramType.components);
|
return resolveAddresses(resolver, value, paramType.components);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (paramType.baseType === "array") {
|
if (paramType.baseType === "array") {
|
||||||
if (!Array.isArray(value)) { throw new Error("invalid value for 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.all(value.map((v) => resolveAddresses(resolver, v, paramType.arrayChildren)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(value);
|
return Promise.resolve(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _populateTransaction(contract: Contract, fragment: FunctionFragment, args: Array<any>, overrides?: Overrides): Promise<UnsignedTransaction> {
|
async function populateTransaction(contract: Contract, fragment: FunctionFragment, args: Array<any>): Promise<PopulatedTransaction> {
|
||||||
overrides = shallowCopy(overrides);
|
|
||||||
|
|
||||||
// Wait for all dependency addresses to be resolved (prefer the signer over the provider)
|
// If an extra argument is given, it is overrides
|
||||||
|
let overrides: CallOverrides = { };
|
||||||
|
if (args.length === fragment.inputs.length + 1 && typeof(args[args.length - 1]) === "object") {
|
||||||
|
overrides = shallowCopy(args.pop());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the parameter count matches
|
||||||
|
logger.checkArgumentCount(args.length, fragment.inputs.length, "passed to contract");
|
||||||
|
|
||||||
|
// Populate "from" override (allow promises)
|
||||||
|
if (contract.signer) {
|
||||||
|
if (overrides.from) {
|
||||||
|
// Contracts with a Signer are from the Signer's frame-of-reference;
|
||||||
|
// but we allow overriding "from" if it matches the signer
|
||||||
|
overrides.from = resolveProperties({
|
||||||
|
override: resolveName(contract.signer, overrides.from),
|
||||||
|
signer: contract.signer.getAddress()
|
||||||
|
}).then(async (check) => {
|
||||||
|
if (getAddress(check.signer) !== check.override) {
|
||||||
|
logger.throwError("Contract with a Signer cannot override from", Logger.errors.UNSUPPORTED_OPERATION, {
|
||||||
|
operation: "overrides.from"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return check.override;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
overrides.from = contract.signer.getAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (overrides.from) {
|
||||||
|
overrides.from = resolveName(contract.provider, overrides.from);
|
||||||
|
|
||||||
|
//} else {
|
||||||
|
// Contracts without a signer can override "from", and if
|
||||||
|
// unspecified the zero address is used
|
||||||
|
//overrides.from = AddressZero;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all dependencies to be resolved (prefer the signer over the provider)
|
||||||
const resolved = await resolveProperties({
|
const resolved = await resolveProperties({
|
||||||
args: resolveAddresses(contract.signer || contract.provider, args, fragment.inputs),
|
args: resolveAddresses(contract.signer || contract.provider, args, fragment.inputs),
|
||||||
address: contract.resolvedAddress,
|
address: contract.resolvedAddress,
|
||||||
@ -120,28 +191,43 @@ async function _populateTransaction(contract: Contract, fragment: FunctionFragme
|
|||||||
});
|
});
|
||||||
|
|
||||||
// The ABI coded transaction
|
// The ABI coded transaction
|
||||||
const tx: UnsignedTransaction = {
|
const tx: PopulatedTransaction = {
|
||||||
data: contract.interface.encodeFunctionData(fragment, resolved.args),
|
data: contract.interface.encodeFunctionData(fragment, resolved.args),
|
||||||
to: resolved.address
|
to: resolved.address
|
||||||
};
|
};
|
||||||
|
|
||||||
// Resolved Overrides
|
// Resolved Overrides
|
||||||
const ro = resolved.overrides;
|
const ro = resolved.overrides;
|
||||||
|
|
||||||
|
// Populate simple overrides
|
||||||
if (ro.nonce != null) { tx.nonce = BigNumber.from(ro.nonce).toNumber(); }
|
if (ro.nonce != null) { tx.nonce = BigNumber.from(ro.nonce).toNumber(); }
|
||||||
if (ro.gasLimit != null) { tx.gasLimit = BigNumber.from(ro.gasLimit); }
|
if (ro.gasLimit != null) { tx.gasLimit = BigNumber.from(ro.gasLimit); }
|
||||||
if (ro.gasPrice != null) { tx.gasPrice = BigNumber.from(ro.gasPrice); }
|
if (ro.gasPrice != null) { tx.gasPrice = BigNumber.from(ro.gasPrice); }
|
||||||
|
if (ro.from != null) { tx.from = ro.from; }
|
||||||
|
|
||||||
// If there was no gasLimit override, but the ABI specifies one use it
|
// If there was no "gasLimit" override, but the ABI specifies a default, use it
|
||||||
if (tx.gasLimit == null && fragment.gas != null) {
|
if (tx.gasLimit == null && fragment.gas != null) {
|
||||||
tx.gasLimit = BigNumber.from(fragment.gas).add(21000);
|
tx.gasLimit = BigNumber.from(fragment.gas).add(21000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Populate "value" override
|
||||||
|
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: overrides.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
tx.value = roValue;
|
||||||
|
}
|
||||||
|
|
||||||
// Remvoe the overrides
|
// Remvoe the overrides
|
||||||
delete overrides.nonce;
|
delete overrides.nonce;
|
||||||
delete overrides.gasLimit;
|
delete overrides.gasLimit;
|
||||||
delete overrides.gasPrice;
|
delete overrides.gasPrice;
|
||||||
|
delete overrides.from;
|
||||||
// @TODO: Maybe move all tx property validation to the Signer and Provider?
|
delete overrides.value;
|
||||||
|
|
||||||
// Make sure there are no stray overrides, which may indicate a
|
// Make sure there are no stray overrides, which may indicate a
|
||||||
// typo or using an unsupported key.
|
// typo or using an unsupported key.
|
||||||
@ -149,122 +235,64 @@ async function _populateTransaction(contract: Contract, fragment: FunctionFragme
|
|||||||
if (leftovers.length) {
|
if (leftovers.length) {
|
||||||
logger.throwError(`cannot override ${ leftovers.map((l) => JSON.stringify(l)).join(",") }`, Logger.errors.UNSUPPORTED_OPERATION, {
|
logger.throwError(`cannot override ${ leftovers.map((l) => JSON.stringify(l)).join(",") }`, Logger.errors.UNSUPPORTED_OPERATION, {
|
||||||
operation: "overrides",
|
operation: "overrides",
|
||||||
keys: leftovers
|
overrides: leftovers
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return tx;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async function populateTransaction(contract: Contract, fragment: FunctionFragment, args: Array<any>, overrides?: PayableOverrides): Promise<UnsignedTransaction> {
|
|
||||||
overrides = shallowCopy(overrides);
|
|
||||||
|
|
||||||
// If the contract was just deployed, wait until it is minded
|
|
||||||
if (contract.deployTransaction != null) {
|
|
||||||
await contract._deployed();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolved Overrides (keep value for errors)
|
|
||||||
const ro = await resolveProperties(overrides);
|
|
||||||
const value = overrides.value;
|
|
||||||
delete overrides.value;
|
|
||||||
|
|
||||||
const tx = await _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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
tx.value = roValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function populateCallTransaction(contract: Contract, fragment: FunctionFragment, args: Array<any>, overrides?: CallOverrides): Promise<UnsignedTransaction> {
|
|
||||||
overrides = shallowCopy(overrides);
|
|
||||||
|
|
||||||
// If the contract was just deployed, wait until it is minded
|
|
||||||
if (contract.deployTransaction != null) {
|
|
||||||
let blockTag = undefined;
|
|
||||||
if (overrides.blockTag) { blockTag = await overrides.blockTag; }
|
|
||||||
await contract._deployed(blockTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolved Overrides
|
|
||||||
delete overrides.blockTag;
|
|
||||||
const ro = await resolveProperties(overrides);
|
|
||||||
delete overrides.from;
|
|
||||||
|
|
||||||
const tx = await populateTransaction(contract, fragment, args, overrides);
|
|
||||||
if (ro.from) { (<any>tx).from = this.interface.constructor.getAddress(ro.from); }
|
|
||||||
|
|
||||||
return tx;
|
return tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function buildPopulate(contract: Contract, fragment: FunctionFragment): ContractFunction<UnsignedTransaction> {
|
function buildPopulate(contract: Contract, fragment: FunctionFragment): ContractFunction<PopulatedTransaction> {
|
||||||
const populate = (fragment.constant) ? populateCallTransaction: populateTransaction;
|
return async function(...args: Array<any>): Promise<PopulatedTransaction> {
|
||||||
return async function(...args: Array<any>): Promise<UnsignedTransaction> {
|
return populateTransaction(contract, fragment, args);
|
||||||
let overrides: CallOverrides = 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: Contract, fragment: FunctionFragment): ContractFunction<BigNumber> {
|
function buildEstimate(contract: Contract, fragment: FunctionFragment): ContractFunction<BigNumber> {
|
||||||
const signerOrProvider = (contract.signer || contract.provider);
|
const signerOrProvider = (contract.signer || contract.provider);
|
||||||
const populate = (fragment.constant) ? populateCallTransaction: populateTransaction;
|
|
||||||
return async function(...args: Array<any>): Promise<BigNumber> {
|
return async function(...args: Array<any>): Promise<BigNumber> {
|
||||||
let overrides: CallOverrides = 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) {
|
if (!signerOrProvider) {
|
||||||
logger.throwError("estimate require a provider or signer", Logger.errors.UNSUPPORTED_OPERATION, { operation: "estimateGas" })
|
logger.throwError("estimate require a provider or signer", Logger.errors.UNSUPPORTED_OPERATION, {
|
||||||
|
operation: "estimateGas"
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const tx = await populate(contract, fragment, args, overrides);
|
const tx = await populateTransaction(contract, fragment, args);
|
||||||
return await signerOrProvider.estimateGas(tx);
|
return await signerOrProvider.estimateGas(tx);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildCall(contract: Contract, fragment: FunctionFragment, collapseSimple: boolean): ContractFunction {
|
function buildCall(contract: Contract, fragment: FunctionFragment, collapseSimple: boolean): ContractFunction {
|
||||||
const signerOrProvider = (contract.signer || contract.provider);
|
const signerOrProvider = (contract.signer || contract.provider);
|
||||||
const populate = (fragment.constant) ? populateCallTransaction: populateTransaction;
|
|
||||||
|
|
||||||
return async function(...args: Array<any>): Promise<any> {
|
return async function(...args: Array<any>): Promise<any> {
|
||||||
let overrides: CallOverrides = null;
|
// Extract the "blockTag" override if present
|
||||||
let blockTag = undefined;
|
let blockTag = undefined;
|
||||||
if (args.length === fragment.inputs.length + 1 && typeof(args[args.length - 1]) === "object") {
|
if (args.length === fragment.inputs.length + 1 && typeof(args[args.length - 1]) === "object") {
|
||||||
overrides = shallowCopy(args.pop());
|
const overrides = shallowCopy(args.pop());
|
||||||
if (overrides.blockTag) {
|
if (overrides.blockTag) {
|
||||||
blockTag = await overrides.blockTag;
|
blockTag = await overrides.blockTag;
|
||||||
delete overrides.blockTag;
|
delete overrides.blockTag;
|
||||||
}
|
}
|
||||||
|
args.push(overrides);
|
||||||
}
|
}
|
||||||
logger.checkArgumentCount(args.length, fragment.inputs.length, "passed to contract");
|
|
||||||
|
|
||||||
const tx = await populate(contract, fragment, args, overrides);
|
// If the contract was just deployed, wait until it is mined
|
||||||
const value = await signerOrProvider.call(tx, blockTag);
|
if (contract.deployTransaction != null) {
|
||||||
|
await contract._deployed(blockTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call a node and get the result
|
||||||
|
const tx = await populateTransaction(contract, fragment, args);
|
||||||
|
const result = await signerOrProvider.call(tx, blockTag);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let result = contract.interface.decodeFunctionResult(fragment, value);
|
let value = contract.interface.decodeFunctionResult(fragment, result);
|
||||||
if (collapseSimple && fragment.outputs.length === 1) {
|
if (collapseSimple && fragment.outputs.length === 1) {
|
||||||
result = result[0];
|
value = value[0];
|
||||||
}
|
}
|
||||||
return result;
|
return value;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === Logger.errors.CALL_EXCEPTION) {
|
if (error.code === Logger.errors.CALL_EXCEPTION) {
|
||||||
@ -280,20 +308,17 @@ function buildCall(contract: Contract, fragment: FunctionFragment, collapseSimpl
|
|||||||
function buildSend(contract: Contract, fragment: FunctionFragment): ContractFunction<TransactionResponse> {
|
function buildSend(contract: Contract, fragment: FunctionFragment): ContractFunction<TransactionResponse> {
|
||||||
return async function(...args: Array<any>): Promise<TransactionResponse> {
|
return async function(...args: Array<any>): Promise<TransactionResponse> {
|
||||||
if (!contract.signer) {
|
if (!contract.signer) {
|
||||||
logger.throwError("sending a transaction requires a signer", Logger.errors.UNSUPPORTED_OPERATION, { operation: "sendTransaction" })
|
logger.throwError("sending a transaction requires a signer", Logger.errors.UNSUPPORTED_OPERATION, {
|
||||||
|
operation: "sendTransaction"
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// We allow CallOverrides, since the Signer can accept from
|
// If the contract was just deployed, wait until it is minded
|
||||||
let overrides: CallOverrides = null;
|
if (contract.deployTransaction != null) {
|
||||||
if (args.length === fragment.inputs.length + 1 && typeof(args[args.length - 1]) === "object") {
|
await contract._deployed();
|
||||||
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 = await populateCallTransaction(contract, fragment, args, overrides);
|
const txRequest = await populateTransaction(contract, fragment, args);
|
||||||
|
|
||||||
const tx = await contract.signer.sendTransaction(txRequest);
|
const tx = await contract.signer.sendTransaction(txRequest);
|
||||||
|
|
||||||
@ -539,7 +564,7 @@ export class Contract {
|
|||||||
|
|
||||||
readonly callStatic: { [ name: string ]: ContractFunction };
|
readonly callStatic: { [ name: string ]: ContractFunction };
|
||||||
readonly estimateGas: { [ name: string ]: ContractFunction<BigNumber> };
|
readonly estimateGas: { [ name: string ]: ContractFunction<BigNumber> };
|
||||||
readonly populateTransaction: { [ name: string ]: ContractFunction<UnsignedTransaction> };
|
readonly populateTransaction: { [ name: string ]: ContractFunction<PopulatedTransaction> };
|
||||||
|
|
||||||
readonly filters: { [ name: string ]: (...args: Array<any>) => EventFilter };
|
readonly filters: { [ name: string ]: (...args: Array<any>) => EventFilter };
|
||||||
|
|
||||||
@ -561,14 +586,17 @@ export class Contract {
|
|||||||
// Wrapped functions to call emit and allow deregistration from the provider
|
// Wrapped functions to call emit and allow deregistration from the provider
|
||||||
_wrappedEmits: { [ eventTag: string ]: (...args: Array<any>) => void };
|
_wrappedEmits: { [ eventTag: string ]: (...args: Array<any>) => void };
|
||||||
|
|
||||||
constructor(addressOrName: string, contractInterface: ContractInterface, signerOrProvider: Signer | Provider) {
|
constructor(addressOrName: string, contractInterface: ContractInterface, signerOrProvider?: Signer | Provider) {
|
||||||
logger.checkNew(new.target, Contract);
|
logger.checkNew(new.target, Contract);
|
||||||
|
|
||||||
// @TODO: Maybe still check the addressOrName looks like a valid address or name?
|
// @TODO: Maybe still check the addressOrName looks like a valid address or name?
|
||||||
//address = getAddress(address);
|
//address = getAddress(address);
|
||||||
defineReadOnly(this, "interface", getStatic<InterfaceFunc>(new.target, "getInterface")(contractInterface));
|
defineReadOnly(this, "interface", getStatic<InterfaceFunc>(new.target, "getInterface")(contractInterface));
|
||||||
|
|
||||||
if (Signer.isSigner(signerOrProvider)) {
|
if (signerOrProvider == null) {
|
||||||
|
defineReadOnly(this, "provider", null);
|
||||||
|
defineReadOnly(this, "signer", null);
|
||||||
|
} else if (Signer.isSigner(signerOrProvider)) {
|
||||||
defineReadOnly(this, "provider", signerOrProvider.provider || null);
|
defineReadOnly(this, "provider", signerOrProvider.provider || null);
|
||||||
defineReadOnly(this, "signer", signerOrProvider);
|
defineReadOnly(this, "signer", signerOrProvider);
|
||||||
} else if (Provider.isProvider(signerOrProvider)) {
|
} else if (Provider.isProvider(signerOrProvider)) {
|
||||||
@ -623,10 +651,12 @@ export class Contract {
|
|||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
defineReadOnly(this, "resolvedAddress", Promise.resolve((<any>(this.interface.constructor)).getAddress(addressOrName)));
|
defineReadOnly(this, "resolvedAddress", Promise.resolve(getAddress(addressOrName)));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Without a provider, we cannot use ENS names
|
// Without a provider, we cannot use ENS names
|
||||||
logger.throwArgumentError("provider is required to use non-address contract address", "addressOrName", addressOrName);
|
logger.throwError("provider is required to use ENS name as contract address", Logger.errors.UNSUPPORTED_OPERATION, {
|
||||||
|
operation: "new Contract"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -761,7 +791,7 @@ export class Contract {
|
|||||||
logger.throwError("sending a transactions require a signer", Logger.errors.UNSUPPORTED_OPERATION, { operation: "sendTransaction(fallback)" })
|
logger.throwError("sending a transactions require a signer", Logger.errors.UNSUPPORTED_OPERATION, { operation: "sendTransaction(fallback)" })
|
||||||
}
|
}
|
||||||
|
|
||||||
const tx: TransactionRequest = shallowCopy(overrides || {});
|
const tx: Deferrable<TransactionRequest> = shallowCopy(overrides || {});
|
||||||
|
|
||||||
["from", "to"].forEach(function(key) {
|
["from", "to"].forEach(function(key) {
|
||||||
if ((<any>tx)[key] == null) { return; }
|
if ((<any>tx)[key] == null) { return; }
|
||||||
@ -1068,8 +1098,8 @@ export class ContractFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @TODO: Future; rename to populteTransaction?
|
// @TODO: Future; rename to populteTransaction?
|
||||||
getDeployTransaction(...args: Array<any>): UnsignedTransaction {
|
getDeployTransaction(...args: Array<any>): TransactionRequest {
|
||||||
let tx: UnsignedTransaction = { };
|
let tx: TransactionRequest = { };
|
||||||
|
|
||||||
// If we have 1 additional argument, we allow transaction overrides
|
// If we have 1 additional argument, we allow transaction overrides
|
||||||
if (args.length === this.interface.deploy.inputs.length + 1 && typeof(args[args.length - 1]) === "object") {
|
if (args.length === this.interface.deploy.inputs.length + 1 && typeof(args[args.length - 1]) === "object") {
|
||||||
|
@ -49,6 +49,8 @@ import {
|
|||||||
PayableOverrides,
|
PayableOverrides,
|
||||||
CallOverrides,
|
CallOverrides,
|
||||||
|
|
||||||
|
PopulatedTransaction,
|
||||||
|
|
||||||
ContractInterface
|
ContractInterface
|
||||||
} from "@ethersproject/contracts";
|
} from "@ethersproject/contracts";
|
||||||
|
|
||||||
@ -100,6 +102,8 @@ export {
|
|||||||
PayableOverrides,
|
PayableOverrides,
|
||||||
CallOverrides,
|
CallOverrides,
|
||||||
|
|
||||||
|
PopulatedTransaction,
|
||||||
|
|
||||||
ContractInterface,
|
ContractInterface,
|
||||||
|
|
||||||
BigNumberish,
|
BigNumberish,
|
||||||
|
@ -58,6 +58,8 @@ export {
|
|||||||
PayableOverrides,
|
PayableOverrides,
|
||||||
CallOverrides,
|
CallOverrides,
|
||||||
|
|
||||||
|
PopulatedTransaction,
|
||||||
|
|
||||||
ContractInterface,
|
ContractInterface,
|
||||||
|
|
||||||
BigNumberish,
|
BigNumberish,
|
||||||
|
@ -35,6 +35,7 @@ import { CoerceFunc } from "@ethersproject/abi";
|
|||||||
import { Bytes, BytesLike, Hexable } from "@ethersproject/bytes"
|
import { Bytes, BytesLike, Hexable } from "@ethersproject/bytes"
|
||||||
import { Mnemonic } from "@ethersproject/hdnode";
|
import { Mnemonic } from "@ethersproject/hdnode";
|
||||||
import { EncryptOptions, ProgressCallback } from "@ethersproject/json-wallets";
|
import { EncryptOptions, ProgressCallback } from "@ethersproject/json-wallets";
|
||||||
|
import { Deferrable } from "@ethersproject/properties";
|
||||||
import { Utf8ErrorFunc } from "@ethersproject/strings";
|
import { Utf8ErrorFunc } from "@ethersproject/strings";
|
||||||
import { ConnectionInfo, FetchJsonResponse, OnceBlockable, OncePollable, PollOptions } from "@ethersproject/web";
|
import { ConnectionInfo, FetchJsonResponse, OnceBlockable, OncePollable, PollOptions } from "@ethersproject/web";
|
||||||
|
|
||||||
@ -183,6 +184,8 @@ export {
|
|||||||
|
|
||||||
Mnemonic,
|
Mnemonic,
|
||||||
|
|
||||||
|
Deferrable,
|
||||||
|
|
||||||
Utf8ErrorFunc,
|
Utf8ErrorFunc,
|
||||||
|
|
||||||
ConnectionInfo,
|
ConnectionInfo,
|
||||||
|
@ -61,11 +61,11 @@ export class NonceManager extends ethers.Signer {
|
|||||||
return this.signer.signMessage(message);;
|
return this.signer.signMessage(message);;
|
||||||
}
|
}
|
||||||
|
|
||||||
signTransaction(transaction: ethers.providers.TransactionRequest): Promise<string> {
|
signTransaction(transaction: ethers.utils.Deferrable<ethers.providers.TransactionRequest>): Promise<string> {
|
||||||
return this.signer.signTransaction(transaction);
|
return this.signer.signTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendTransaction(transaction: ethers.providers.TransactionRequest): Promise<ethers.providers.TransactionResponse> {
|
sendTransaction(transaction: ethers.utils.Deferrable<ethers.providers.TransactionRequest>): Promise<ethers.providers.TransactionResponse> {
|
||||||
if (transaction.nonce == null) {
|
if (transaction.nonce == null) {
|
||||||
transaction = ethers.utils.shallowCopy(transaction);
|
transaction = ethers.utils.shallowCopy(transaction);
|
||||||
transaction.nonce = this.getTransactionCount("pending");
|
transaction.nonce = this.getTransactionCount("pending");
|
||||||
|
@ -22,15 +22,16 @@ export function getStatic<T>(ctor: any, key: string): T {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Resolvable<T> = {
|
export type Deferrable<T> = {
|
||||||
[P in keyof T]: T[P] | Promise<T[P]>;
|
[ K in keyof T ]: T[K] | Promise<T[K]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type Result = { key: string, value: any};
|
type Result = { key: string, value: any};
|
||||||
|
|
||||||
export async function resolveProperties<T>(object: Readonly<Resolvable<T>>): Promise<T> {
|
export async function resolveProperties<T>(object: Readonly<Deferrable<T>>): Promise<T> {
|
||||||
const promises: Array<Promise<Result>> = Object.keys(object).map((key) => {
|
const promises: Array<Promise<Result>> = Object.keys(object).map((key) => {
|
||||||
const value = object[<keyof Resolvable<T>>key];
|
const value = object[<keyof Deferrable<T>>key];
|
||||||
return Promise.resolve(value).then((v) => ({ key: key, value: v }));
|
return Promise.resolve(value).then((v) => ({ key: key, value: v }));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import { BigNumber, BigNumberish } from "@ethersproject/bignumber";
|
|||||||
import { arrayify, hexDataLength, hexlify, hexValue, isHexString } from "@ethersproject/bytes";
|
import { arrayify, hexDataLength, hexlify, hexValue, isHexString } from "@ethersproject/bytes";
|
||||||
import { namehash } from "@ethersproject/hash";
|
import { namehash } from "@ethersproject/hash";
|
||||||
import { getNetwork, Network, Networkish } from "@ethersproject/networks";
|
import { getNetwork, Network, Networkish } from "@ethersproject/networks";
|
||||||
import { defineReadOnly, getStatic, resolveProperties } from "@ethersproject/properties";
|
import { Deferrable, defineReadOnly, getStatic, resolveProperties } from "@ethersproject/properties";
|
||||||
import { Transaction } from "@ethersproject/transactions";
|
import { Transaction } from "@ethersproject/transactions";
|
||||||
import { toUtf8String } from "@ethersproject/strings";
|
import { toUtf8String } from "@ethersproject/strings";
|
||||||
import { poll } from "@ethersproject/web";
|
import { poll } from "@ethersproject/web";
|
||||||
@ -678,7 +678,7 @@ export class BaseProvider extends Provider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getTransactionRequest(transaction: TransactionRequest | Promise<TransactionRequest>): Promise<Transaction> {
|
async _getTransactionRequest(transaction: Deferrable<TransactionRequest>): Promise<Transaction> {
|
||||||
const values: any = await transaction;
|
const values: any = await transaction;
|
||||||
|
|
||||||
const tx: any = { };
|
const tx: any = { };
|
||||||
@ -723,8 +723,7 @@ export class BaseProvider extends Provider {
|
|||||||
return this.formatter.filter(await resolveProperties(result));
|
return this.formatter.filter(await resolveProperties(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async call(transaction: Deferrable<TransactionRequest>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string> {
|
||||||
async call(transaction: TransactionRequest | Promise<TransactionRequest>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string> {
|
|
||||||
await this.ready;
|
await this.ready;
|
||||||
const params = await resolveProperties({
|
const params = await resolveProperties({
|
||||||
transaction: this._getTransactionRequest(transaction),
|
transaction: this._getTransactionRequest(transaction),
|
||||||
@ -733,7 +732,7 @@ export class BaseProvider extends Provider {
|
|||||||
return hexlify(await this.perform("call", params));
|
return hexlify(await this.perform("call", params));
|
||||||
}
|
}
|
||||||
|
|
||||||
async estimateGas(transaction: TransactionRequest | Promise<TransactionRequest>): Promise<BigNumber> {
|
async estimateGas(transaction: Deferrable<TransactionRequest>): Promise<BigNumber> {
|
||||||
await this.ready;
|
await this.ready;
|
||||||
const params = await resolveProperties({
|
const params = await resolveProperties({
|
||||||
transaction: this._getTransactionRequest(transaction)
|
transaction: this._getTransactionRequest(transaction)
|
||||||
|
@ -7,7 +7,7 @@ import { Signer } from "@ethersproject/abstract-signer";
|
|||||||
import { BigNumber } from "@ethersproject/bignumber";
|
import { BigNumber } from "@ethersproject/bignumber";
|
||||||
import { Bytes, hexlify, hexValue } from "@ethersproject/bytes";
|
import { Bytes, hexlify, hexValue } from "@ethersproject/bytes";
|
||||||
import { Network, Networkish } from "@ethersproject/networks";
|
import { Network, Networkish } from "@ethersproject/networks";
|
||||||
import { checkProperties, deepCopy, defineReadOnly, getStatic, resolveProperties, shallowCopy } from "@ethersproject/properties";
|
import { checkProperties, deepCopy, Deferrable, defineReadOnly, getStatic, resolveProperties, shallowCopy } from "@ethersproject/properties";
|
||||||
import { toUtf8Bytes } from "@ethersproject/strings";
|
import { toUtf8Bytes } from "@ethersproject/strings";
|
||||||
import { ConnectionInfo, fetchJson, poll } from "@ethersproject/web";
|
import { ConnectionInfo, fetchJson, poll } from "@ethersproject/web";
|
||||||
|
|
||||||
@ -99,7 +99,7 @@ export class JsonRpcSigner extends Signer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sendUncheckedTransaction(transaction: TransactionRequest): Promise<string> {
|
sendUncheckedTransaction(transaction: Deferrable<TransactionRequest>): Promise<string> {
|
||||||
transaction = shallowCopy(transaction);
|
transaction = shallowCopy(transaction);
|
||||||
|
|
||||||
let fromAddress = this.getAddress().then((address) => {
|
let fromAddress = this.getAddress().then((address) => {
|
||||||
@ -149,13 +149,13 @@ export class JsonRpcSigner extends Signer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
signTransaction(transaction: TransactionRequest): Promise<string> {
|
signTransaction(transaction: Deferrable<TransactionRequest>): Promise<string> {
|
||||||
return logger.throwError("signing transactions is unsupported", Logger.errors.UNSUPPORTED_OPERATION, {
|
return logger.throwError("signing transactions is unsupported", Logger.errors.UNSUPPORTED_OPERATION, {
|
||||||
operation: "signTransaction"
|
operation: "signTransaction"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sendTransaction(transaction: TransactionRequest): Promise<TransactionResponse> {
|
sendTransaction(transaction: Deferrable<TransactionRequest>): Promise<TransactionResponse> {
|
||||||
return this.sendUncheckedTransaction(transaction).then((hash) => {
|
return this.sendUncheckedTransaction(transaction).then((hash) => {
|
||||||
return poll(() => {
|
return poll(() => {
|
||||||
return this.provider.getTransaction(hash).then((tx: TransactionResponse) => {
|
return this.provider.getTransaction(hash).then((tx: TransactionResponse) => {
|
||||||
@ -188,7 +188,7 @@ export class JsonRpcSigner extends Signer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class UncheckedJsonRpcSigner extends JsonRpcSigner {
|
class UncheckedJsonRpcSigner extends JsonRpcSigner {
|
||||||
sendTransaction(transaction: TransactionRequest): Promise<TransactionResponse> {
|
sendTransaction(transaction: Deferrable<TransactionRequest>): Promise<TransactionResponse> {
|
||||||
return this.sendUncheckedTransaction(transaction).then((hash) => {
|
return this.sendUncheckedTransaction(transaction).then((hash) => {
|
||||||
return <TransactionResponse>{
|
return <TransactionResponse>{
|
||||||
hash: hash,
|
hash: hash,
|
||||||
|
@ -152,3 +152,148 @@ describe('Test Contract Objects', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// @TODO: Exapnd this
|
||||||
|
describe("Test Contract Transaction Population", function() {
|
||||||
|
const abi = [
|
||||||
|
"function transfer(address to, uint amount)",
|
||||||
|
"function unstake() nonpayable",
|
||||||
|
"function mint() payable",
|
||||||
|
"function balanceOf(address owner) view returns (uint)"
|
||||||
|
];
|
||||||
|
|
||||||
|
const testAddress = "0xdeadbeef00deadbeef01deadbeef02deadbeef03"
|
||||||
|
const testAddressCheck = "0xDEAdbeeF00deAdbeEF01DeAdBEEF02DeADBEEF03";
|
||||||
|
const fireflyAddress = "0x8ba1f109551bD432803012645Ac136ddd64DBA72";
|
||||||
|
|
||||||
|
const contract = new ethers.Contract(testAddress, abi);
|
||||||
|
const contractConnected = contract.connect(ethers.getDefaultProvider());
|
||||||
|
|
||||||
|
it("standard populatation", async function() {
|
||||||
|
const tx = await contract.populateTransaction.balanceOf(testAddress);
|
||||||
|
//console.log(tx);
|
||||||
|
assert.equal(Object.keys(tx).length, 2, "correct number of keys");
|
||||||
|
assert.equal(tx.data, "0x70a08231000000000000000000000000deadbeef00deadbeef01deadbeef02deadbeef03", "data matches");
|
||||||
|
assert.equal(tx.to, testAddressCheck, "to address matches");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows 'from' overrides", async function() {
|
||||||
|
const tx = await contract.populateTransaction.balanceOf(testAddress, {
|
||||||
|
from: testAddress
|
||||||
|
});
|
||||||
|
//console.log(tx);
|
||||||
|
|
||||||
|
assert.equal(Object.keys(tx).length, 3, "correct number of keys");
|
||||||
|
assert.equal(tx.data, "0x70a08231000000000000000000000000deadbeef00deadbeef01deadbeef02deadbeef03", "data matches");
|
||||||
|
assert.equal(tx.to, testAddressCheck, "to address matches");
|
||||||
|
assert.equal((<any>tx).from, testAddressCheck, "from address matches");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows ENS 'from' overrides", async function() {
|
||||||
|
this.timeout(20000);
|
||||||
|
|
||||||
|
const tx = await contractConnected.populateTransaction.balanceOf(testAddress, {
|
||||||
|
from: "ricmoo.firefly.eth"
|
||||||
|
});
|
||||||
|
//console.log(tx);
|
||||||
|
|
||||||
|
assert.equal(Object.keys(tx).length, 3, "correct number of keys");
|
||||||
|
assert.equal(tx.data, "0x70a08231000000000000000000000000deadbeef00deadbeef01deadbeef02deadbeef03", "data matches");
|
||||||
|
assert.equal(tx.to, testAddressCheck, "to address matches");
|
||||||
|
assert.equal((<any>tx).from, fireflyAddress, "from address matches");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows send overrides", async function() {
|
||||||
|
const tx = await contract.populateTransaction.mint({
|
||||||
|
gasLimit: 150000,
|
||||||
|
gasPrice: 1900000000,
|
||||||
|
nonce: 5,
|
||||||
|
value: 1234,
|
||||||
|
from: testAddress
|
||||||
|
});
|
||||||
|
//console.log(tx);
|
||||||
|
|
||||||
|
assert.equal(Object.keys(tx).length, 7, "correct number of keys");
|
||||||
|
assert.equal(tx.data, "0x1249c58b", "data matches");
|
||||||
|
assert.equal(tx.to, testAddressCheck, "to address matches");
|
||||||
|
assert.equal(tx.nonce, 5, "nonce address matches");
|
||||||
|
assert.ok(tx.gasLimit.eq(150000), "gasLimit matches");
|
||||||
|
assert.ok(tx.gasPrice.eq(1900000000), "gasPrice matches");
|
||||||
|
assert.ok(tx.value.eq(1234), "value matches");
|
||||||
|
assert.equal(tx.from, testAddressCheck, "from address matches");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows zero 'value' to non-payable", async function() {
|
||||||
|
const tx = await contract.populateTransaction.unstake({
|
||||||
|
from: testAddress,
|
||||||
|
value: 0
|
||||||
|
});
|
||||||
|
//console.log(tx);
|
||||||
|
|
||||||
|
assert.equal(Object.keys(tx).length, 3, "correct number of keys");
|
||||||
|
assert.equal(tx.data, "0x2def6620", "data matches");
|
||||||
|
assert.equal(tx.to, testAddressCheck, "to address matches");
|
||||||
|
assert.equal(tx.from, testAddressCheck, "from address matches");
|
||||||
|
});
|
||||||
|
|
||||||
|
// @TODO: Add test cases to check for fault cases
|
||||||
|
// - cannot send non-zero value to non-payable
|
||||||
|
// - using the wrong from for a Signer-connected contract
|
||||||
|
it("forbids non-zero 'value' to non-payable", async function() {
|
||||||
|
try {
|
||||||
|
const tx = await contract.populateTransaction.unstake({
|
||||||
|
value: 1
|
||||||
|
});
|
||||||
|
console.log("Tx", tx);
|
||||||
|
assert.ok(false, "throws on non-zero value to non-payable");
|
||||||
|
} catch(error) {
|
||||||
|
assert.ok(error.operation === "overrides.value");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows overriding same 'from' with a Signer", async function() {
|
||||||
|
const contractSigner = contract.connect(testAddress);
|
||||||
|
const tx = await contractSigner.populateTransaction.unstake({
|
||||||
|
from: testAddress
|
||||||
|
});
|
||||||
|
//console.log(tx);
|
||||||
|
|
||||||
|
assert.equal(Object.keys(tx).length, 3, "correct number of keys");
|
||||||
|
assert.equal(tx.data, "0x2def6620", "data matches");
|
||||||
|
assert.equal(tx.to, testAddressCheck, "to address matches");
|
||||||
|
assert.equal(tx.from, testAddressCheck, "from address matches");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("forbids overriding 'from' with a Signer", async function() {
|
||||||
|
const contractSigner = contract.connect(testAddress);
|
||||||
|
try {
|
||||||
|
const tx = await contractSigner.populateTransaction.unstake({
|
||||||
|
from: fireflyAddress
|
||||||
|
});
|
||||||
|
console.log("Tx", tx);
|
||||||
|
assert.ok(false, "throws on non-zero value to non-payable");
|
||||||
|
} catch(error) {
|
||||||
|
assert.ok(error.operation === "overrides.from");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Test Contract interaction inside Grid-deployed Geth
|
||||||
|
describe("Test Contract Life-Cycle", function() {
|
||||||
|
this.timeout(10000);
|
||||||
|
|
||||||
|
let blockNumber: number = null;
|
||||||
|
|
||||||
|
before(async function() {
|
||||||
|
const provider = ethers.getDefaultProvider();
|
||||||
|
blockNumber = await provider.getBlockNumber();
|
||||||
|
//console.log(blockNumber);
|
||||||
|
this.skip();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("says hi", function() {
|
||||||
|
console.log("hi", blockNumber);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user