Initial support for fallback and receive methods on contracts.
This commit is contained in:
parent
67d2af809e
commit
c3f8e8ac07
@ -3,7 +3,9 @@ import assert from "assert";
|
||||
|
||||
import { getProvider, setupProviders } from "./create-provider.js";
|
||||
|
||||
import { Contract, EventLog, Typed, Wallet } from "../index.js";
|
||||
import {
|
||||
Contract, EventLog, isError, Typed, Wallet
|
||||
} from "../index.js";
|
||||
import type { ContractEventPayload, ContractEventName, Log } from "../index.js";
|
||||
|
||||
setupProviders();
|
||||
@ -22,7 +24,7 @@ describe("Test Contract", function() {
|
||||
"function testErrorString(bool pass, string calldata message) pure returns (uint256)",
|
||||
"function testPanic(uint256 code) returns (uint256)",
|
||||
"function testEvent(uint256 valueUint256, address valueAddress, string valueString, bytes valueBytes) public",
|
||||
"function testCallAdd(uint256 a, uint256 b) pure returns (uint256 result)"
|
||||
"function testCallAdd(uint256 a, uint256 b) pure returns (uint256 result)",
|
||||
];
|
||||
|
||||
it("tests contract calls", async function() {
|
||||
@ -319,3 +321,146 @@ describe("Test Contract Interface", function() {
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
type TestContractFallbackResult = {
|
||||
data: string;
|
||||
} | {
|
||||
error: string;
|
||||
};
|
||||
|
||||
type TestContractFallback = {
|
||||
name: string;
|
||||
address: string;
|
||||
abi: Array<string>;
|
||||
sendNone: TestContractFallbackResult;
|
||||
sendData: TestContractFallbackResult;
|
||||
sendValue: TestContractFallbackResult;
|
||||
sendDataAndValue: TestContractFallbackResult;
|
||||
};
|
||||
|
||||
describe("Test Contract Fallback", function() {
|
||||
const tests: Array<TestContractFallback> = [
|
||||
{
|
||||
name: "none",
|
||||
address: "0x0ccdace3d8353fed9b87a2d63c40452923ccdae5",
|
||||
abi: [ ],
|
||||
sendNone: { error: "no fallback" },
|
||||
sendData: { error: "no fallback" },
|
||||
sendValue: { error: "no fallback" },
|
||||
sendDataAndValue: { error: "no fallback" },
|
||||
},
|
||||
{
|
||||
name: "non-payable fallback",
|
||||
address: "0x3f10193f79a639b11ec9d2ab42a25a4a905a8870",
|
||||
abi: [
|
||||
"fallback()"
|
||||
],
|
||||
sendNone: { data: "0x" },
|
||||
sendData: { data: "0x1234" },
|
||||
sendValue: { error: "overrides.value" },
|
||||
sendDataAndValue: { error: "overrides.value" },
|
||||
},
|
||||
{
|
||||
name: "payable fallback",
|
||||
address: "0xe2de6b97c5eb9fee8a47ca6c0fa642331e0b6330",
|
||||
abi: [
|
||||
"fallback() payable"
|
||||
],
|
||||
sendNone: { data: "0x" },
|
||||
sendData: { data: "0x1234" },
|
||||
sendValue: { data: "0x" },
|
||||
sendDataAndValue: { data: "0x1234" },
|
||||
},
|
||||
{
|
||||
name: "receive-only",
|
||||
address: "0xf8f2afbbe37f6a4520e4db7f99495655aa31229b",
|
||||
abi: [
|
||||
"receive()"
|
||||
],
|
||||
sendNone: { data: "0x" },
|
||||
sendData: { error: "overrides.data" },
|
||||
sendValue: { data: "0x" },
|
||||
sendDataAndValue: { error: "overrides.data" },
|
||||
},
|
||||
{
|
||||
name: "receive and payable fallback",
|
||||
address: "0x7d97ca5d9dea1cd0364f1d493252006a3c4e18a0",
|
||||
abi: [
|
||||
"fallback() payable",
|
||||
"receive()"
|
||||
],
|
||||
sendNone: { data: "0x" },
|
||||
sendData: { data: "0x1234" },
|
||||
sendValue: { data: "0x" },
|
||||
sendDataAndValue: { data: "0x1234" },
|
||||
},
|
||||
{
|
||||
name: "receive and non-payable fallback",
|
||||
address: "0x5b59d934f0d22b15e73b5d6b9ae83486b70df67e",
|
||||
abi: [
|
||||
"fallback() payable",
|
||||
"receive()"
|
||||
],
|
||||
sendNone: { data: "0x" },
|
||||
sendData: { data: "0x" },
|
||||
sendValue: { data: "0x" },
|
||||
sendDataAndValue: { error: "overrides.value" },
|
||||
},
|
||||
];
|
||||
|
||||
const provider = getProvider("InfuraProvider", "goerli");
|
||||
|
||||
const testGroups: Array<{ group: "sendNone" | "sendData" | "sendValue" | "sendDataAndValue", tx: any }> = [
|
||||
{
|
||||
group: "sendNone",
|
||||
tx: { }
|
||||
},
|
||||
{
|
||||
group: "sendData",
|
||||
tx: { data: "0x1234" }
|
||||
},
|
||||
{
|
||||
group: "sendValue",
|
||||
tx: { value: 123 }
|
||||
},
|
||||
{
|
||||
group: "sendDataAndValue",
|
||||
tx: { data: "0x1234", value: 123 }
|
||||
},
|
||||
];
|
||||
|
||||
for (const { group, tx } of testGroups) {
|
||||
for (const test of tests) {
|
||||
const { name, address, abi } = test;
|
||||
const send = test[group];
|
||||
|
||||
const contract = new Contract(address, abi, provider);
|
||||
it(`test contract fallback checks: ${ group } - ${ name }`, async function() {
|
||||
const func = async function() {
|
||||
if (abi.length === 0) {
|
||||
throw new Error("no fallback");
|
||||
}
|
||||
assert.ok(contract.fallback);
|
||||
return await contract.fallback.populateTransaction(tx)
|
||||
};
|
||||
|
||||
if ("data" in send) {
|
||||
await func();
|
||||
//const result = await func();
|
||||
//@TODO: Test for the correct populated tx
|
||||
//console.log(result);
|
||||
assert.ok(true);
|
||||
} else {
|
||||
assert.rejects(func, function(error: any) {
|
||||
if (error.message === send.error) { return true; }
|
||||
if (isError(error, "INVALID_ARGUMENT")) {
|
||||
return error.argument === send.error;
|
||||
}
|
||||
console.log("EE", error);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -117,7 +117,7 @@ function setify(items: Array<string>): ReadonlySet<string> {
|
||||
const _kwVisib = "constant external internal payable private public pure view";
|
||||
const KwVisib = setify(_kwVisib.split(" "));
|
||||
|
||||
const _kwTypes = "constructor error event function struct";
|
||||
const _kwTypes = "constructor error event fallback function receive struct";
|
||||
const KwTypes = setify(_kwTypes.split(" "));
|
||||
|
||||
const _kwModifiers = "calldata memory storage payable indexed";
|
||||
@ -420,7 +420,7 @@ function consumeKeywords(tokens: TokenString, allowed?: ReadonlySet<string>): Re
|
||||
}
|
||||
|
||||
// ...all visibility keywords, returning the coalesced mutability
|
||||
function consumeMutability(tokens: TokenString): string {
|
||||
function consumeMutability(tokens: TokenString): "payable" | "nonpayable" | "view" | "pure" {
|
||||
let modifiers = consumeKeywords(tokens, KwVisib);
|
||||
|
||||
// Detect conflicting modifiers
|
||||
@ -506,6 +506,7 @@ const ParamTypeInternal = "_ParamTypeInternal";
|
||||
const ErrorFragmentInternal = "_ErrorInternal";
|
||||
const EventFragmentInternal = "_EventInternal";
|
||||
const ConstructorFragmentInternal = "_ConstructorInternal";
|
||||
const FallbackFragmentInternal = "_FallbackInternal";
|
||||
const FunctionFragmentInternal = "_FunctionInternal";
|
||||
const StructFragmentInternal = "_StructInternal";
|
||||
|
||||
@ -885,7 +886,7 @@ export class ParamType {
|
||||
/**
|
||||
* The type of a [[Fragment]].
|
||||
*/
|
||||
export type FragmentType = "constructor" | "error" | "event" | "function" | "struct";
|
||||
export type FragmentType = "constructor" | "error" | "event" | "fallback" | "function" | "struct";
|
||||
|
||||
/**
|
||||
* An abstract class to represent An individual fragment from a parse ABI.
|
||||
@ -921,39 +922,50 @@ export abstract class Fragment {
|
||||
*/
|
||||
static from(obj: any): Fragment {
|
||||
if (typeof(obj) === "string") {
|
||||
|
||||
// Try parsing JSON...
|
||||
try {
|
||||
Fragment.from(JSON.parse(obj));
|
||||
} catch (e) { }
|
||||
|
||||
// ...otherwise, use the human-readable lexer
|
||||
return Fragment.from(lex(obj));
|
||||
}
|
||||
|
||||
if (obj instanceof TokenString) {
|
||||
const type = obj.popKeyword(KwTypes);
|
||||
// Human-readable ABI (already lexed)
|
||||
|
||||
const type = obj.peekKeyword(KwTypes);
|
||||
|
||||
switch (type) {
|
||||
case "constructor": return ConstructorFragment.from(obj);
|
||||
case "error": return ErrorFragment.from(obj);
|
||||
case "event": return EventFragment.from(obj);
|
||||
case "fallback": case "receive":
|
||||
return FallbackFragment.from(obj);
|
||||
case "function": return FunctionFragment.from(obj);
|
||||
case "struct": return StructFragment.from(obj);
|
||||
}
|
||||
|
||||
throw new Error(`unsupported type: ${ type }`);
|
||||
}
|
||||
} else if (typeof(obj) === "object") {
|
||||
// JSON ABI
|
||||
|
||||
if (typeof(obj) === "object") {
|
||||
switch (obj.type) {
|
||||
case "constructor": return ConstructorFragment.from(obj);
|
||||
case "error": return ErrorFragment.from(obj);
|
||||
case "event": return EventFragment.from(obj);
|
||||
case "fallback": case "receive":
|
||||
return FallbackFragment.from(obj);
|
||||
case "function": return FunctionFragment.from(obj);
|
||||
case "struct": return StructFragment.from(obj);
|
||||
}
|
||||
throw new Error(`not implemented yet: ${ obj.type }`);
|
||||
|
||||
assert(false, `unsupported type: ${ obj.type }`, "UNSUPPORTED_OPERATION", {
|
||||
operation: "Fragment.from"
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error(`unsupported type: ${ obj }`);
|
||||
assertArgument(false, "unsupported frgament object", "obj", obj);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1202,6 +1214,101 @@ export class ConstructorFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Fragment which represents a method.
|
||||
*/
|
||||
export class FallbackFragment extends Fragment {
|
||||
|
||||
/**
|
||||
* If the function can be sent value during invocation.
|
||||
*/
|
||||
readonly payable!: boolean;
|
||||
|
||||
constructor(guard: any, inputs: ReadonlyArray<ParamType>, payable: boolean) {
|
||||
super(guard, "fallback", inputs);
|
||||
Object.defineProperty(this, internal, { value: FallbackFragmentInternal });
|
||||
defineProperties<FallbackFragment>(this, { payable });
|
||||
}
|
||||
|
||||
format(format?: FormatType): string {
|
||||
const type = ((this.inputs.length === 0) ? "receive": "fallback");
|
||||
|
||||
if (format === "json") {
|
||||
const stateMutability = (this.payable ? "payable": "nonpayable");
|
||||
return JSON.stringify({ type, stateMutability });
|
||||
}
|
||||
|
||||
return `${ type }()${ this.payable ? " payable": "" }`;
|
||||
}
|
||||
|
||||
static from(obj: any): FallbackFragment {
|
||||
if (FallbackFragment.isFragment(obj)) { return obj; }
|
||||
|
||||
if (typeof(obj) === "string") {
|
||||
return FallbackFragment.from(lex(obj));
|
||||
|
||||
} else if (obj instanceof TokenString) {
|
||||
const errorObj = obj.toString();
|
||||
|
||||
const topIsValid = obj.peekKeyword(setify([ "fallback", "receive" ]));
|
||||
assertArgument(topIsValid, "type must be fallback or receive", "obj", errorObj);
|
||||
|
||||
const type = obj.popKeyword(setify([ "fallback", "receive" ]));
|
||||
|
||||
// receive()
|
||||
if (type === "receive") {
|
||||
const inputs = consumeParams(obj);
|
||||
assertArgument(inputs.length === 0, `receive cannot have arguments`, "obj.inputs", inputs);
|
||||
consumeKeywords(obj, setify([ "payable" ]));
|
||||
consumeEoi(obj);
|
||||
return new FallbackFragment(_guard, [ ], true);
|
||||
}
|
||||
|
||||
// fallback() [payable]
|
||||
// fallback(bytes) [payable] returns (bytes)
|
||||
let inputs = consumeParams(obj);
|
||||
if (inputs.length) {
|
||||
assertArgument(inputs.length === 1 && inputs[0].type === "bytes",
|
||||
"invalid fallback inputs", "obj.inputs",
|
||||
inputs.map((i) => i.format("minimal")).join(", "));
|
||||
} else {
|
||||
inputs = [ ParamType.from("bytes") ];
|
||||
}
|
||||
|
||||
const mutability = consumeMutability(obj);
|
||||
assertArgument(mutability === "nonpayable" || mutability === "payable", "fallback cannot be constants", "obj.stateMutability", mutability);
|
||||
|
||||
if (consumeKeywords(obj, setify([ "returns" ])).has("returns")) {
|
||||
const outputs = consumeParams(obj);
|
||||
assertArgument(outputs.length === 1 && outputs[0].type === "bytes",
|
||||
"invalid fallback outputs", "obj.outputs",
|
||||
outputs.map((i) => i.format("minimal")).join(", "));
|
||||
}
|
||||
|
||||
consumeEoi(obj);
|
||||
|
||||
return new FallbackFragment(_guard, inputs, mutability === "payable");
|
||||
}
|
||||
|
||||
if (obj.type === "receive") {
|
||||
return new FallbackFragment(_guard, [ ], true);
|
||||
}
|
||||
|
||||
if (obj.type === "fallback") {
|
||||
const inputs = [ ParamType.from("bytes") ];
|
||||
const payable = (obj.stateMutability === "payable");
|
||||
return new FallbackFragment(_guard, inputs, payable);
|
||||
}
|
||||
|
||||
assertArgument(false, "invalid fallback description", "obj", obj);
|
||||
}
|
||||
|
||||
static isFragment(value: any): value is FallbackFragment {
|
||||
return (value && value[internal] === FallbackFragmentInternal);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A Fragment which represents a method.
|
||||
*/
|
||||
@ -1220,10 +1327,10 @@ export class FunctionFragment extends NamedFragment {
|
||||
* The state mutability (e.g. ``payable``, ``nonpayable``, ``view``
|
||||
* or ``pure``)
|
||||
*/
|
||||
readonly stateMutability!: string;
|
||||
readonly stateMutability!: "payable" | "nonpayable" | "view" | "pure";
|
||||
|
||||
/**
|
||||
* If the function can be send a value during invocation.
|
||||
* If the function can be sent value during invocation.
|
||||
*/
|
||||
readonly payable!: boolean;
|
||||
|
||||
@ -1235,7 +1342,7 @@ export class FunctionFragment extends NamedFragment {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
constructor(guard: any, name: string, stateMutability: string, inputs: ReadonlyArray<ParamType>, outputs: ReadonlyArray<ParamType>, gas: null | bigint) {
|
||||
constructor(guard: any, name: string, stateMutability: "payable" | "nonpayable" | "view" | "pure", inputs: ReadonlyArray<ParamType>, outputs: ReadonlyArray<ParamType>, gas: null | bigint) {
|
||||
super(guard, "function", name, inputs);
|
||||
Object.defineProperty(this, internal, { value: FunctionFragmentInternal });
|
||||
outputs = Object.freeze(outputs.slice());
|
||||
|
@ -12,8 +12,8 @@ export { AbiCoder } from "./abi-coder.js";
|
||||
export { decodeBytes32String, encodeBytes32String } from "./bytes32.js";
|
||||
|
||||
export {
|
||||
ConstructorFragment, ErrorFragment, EventFragment, Fragment,
|
||||
FunctionFragment, NamedFragment, ParamType, StructFragment,
|
||||
ConstructorFragment, ErrorFragment, EventFragment, FallbackFragment,
|
||||
Fragment, FunctionFragment, NamedFragment, ParamType, StructFragment,
|
||||
} from "./fragments.js";
|
||||
|
||||
export {
|
||||
|
@ -14,7 +14,10 @@ import {
|
||||
|
||||
import { AbiCoder } from "./abi-coder.js";
|
||||
import { checkResultErrors, Result } from "./coders/abstract-coder.js";
|
||||
import { ConstructorFragment, ErrorFragment, EventFragment, Fragment, FunctionFragment, ParamType } from "./fragments.js";
|
||||
import {
|
||||
ConstructorFragment, ErrorFragment, EventFragment, FallbackFragment,
|
||||
Fragment, FunctionFragment, ParamType
|
||||
} from "./fragments.js";
|
||||
import { Typed } from "./typed.js";
|
||||
|
||||
import type { BigNumberish, BytesLike, CallExceptionError, CallExceptionTransaction } from "../utils/index.js";
|
||||
@ -175,6 +178,16 @@ export class Interface {
|
||||
*/
|
||||
readonly deploy!: ConstructorFragment;
|
||||
|
||||
/**
|
||||
* The Fallback method, if any.
|
||||
*/
|
||||
readonly fallback!: null | FallbackFragment;
|
||||
|
||||
/**
|
||||
* If receiving ether is supported.
|
||||
*/
|
||||
readonly receive!: boolean;
|
||||
|
||||
#errors: Map<string, ErrorFragment>;
|
||||
#events: Map<string, EventFragment>;
|
||||
#functions: Map<string, FunctionFragment>;
|
||||
@ -212,10 +225,13 @@ export class Interface {
|
||||
fragments: Object.freeze(frags)
|
||||
});
|
||||
|
||||
let fallback: null | FallbackFragment = null;
|
||||
let receive = false;
|
||||
|
||||
this.#abiCoder = this.getAbiCoder();
|
||||
|
||||
// Add all fragments by their signature
|
||||
this.fragments.forEach((fragment) => {
|
||||
this.fragments.forEach((fragment, index) => {
|
||||
let bucket: Map<string, Fragment>;
|
||||
switch (fragment.type) {
|
||||
case "constructor":
|
||||
@ -227,6 +243,17 @@ export class Interface {
|
||||
defineProperties<Interface>(this, { deploy: <ConstructorFragment>fragment });
|
||||
return;
|
||||
|
||||
case "fallback":
|
||||
if (fragment.inputs.length === 0) {
|
||||
receive = true;
|
||||
} else {
|
||||
assertArgument(!fallback || (<FallbackFragment>fragment).payable !== fallback.payable,
|
||||
"conflicting fallback fragments", `fragments[${ index }]`, fragment);
|
||||
fallback = <FallbackFragment>fragment;
|
||||
receive = fallback.payable;
|
||||
}
|
||||
return;
|
||||
|
||||
case "function":
|
||||
//checkNames(fragment, "input", fragment.inputs);
|
||||
//checkNames(fragment, "output", (<FunctionFragment>fragment).outputs);
|
||||
@ -259,6 +286,8 @@ export class Interface {
|
||||
deploy: ConstructorFragment.from("constructor()")
|
||||
});
|
||||
}
|
||||
|
||||
defineProperties<Interface>(this, { fallback, receive });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -33,6 +33,8 @@ import type {
|
||||
DeferredTopicFilter
|
||||
} from "./types.js";
|
||||
|
||||
const BN_0 = BigInt(0);
|
||||
|
||||
interface ContractRunnerCaller extends ContractRunner {
|
||||
call: (tx: TransactionRequest) => Promise<string>;
|
||||
}
|
||||
@ -129,25 +131,22 @@ function getProvider(value: null | ContractRunner): null | Provider {
|
||||
/**
|
||||
* @_ignore:
|
||||
*/
|
||||
export async function copyOverrides(arg: any): Promise<Omit<ContractTransaction, "data" | "to">> {
|
||||
export async function copyOverrides<O extends string = "data" | "to">(arg: any, allowed?: Array<string>): Promise<Omit<ContractTransaction, O>> {
|
||||
|
||||
// Create a shallow copy (we'll deep-ify anything needed during normalizing)
|
||||
const overrides = copyRequest(Typed.dereference(arg, "overrides"));
|
||||
|
||||
// Some sanity checking; these are what these methods adds
|
||||
//if ((<any>overrides).to) {
|
||||
if (overrides.to) {
|
||||
assertArgument(false, "cannot override to", "overrides.to", overrides.to);
|
||||
} else if (overrides.data) {
|
||||
assertArgument(false, "cannot override data", "overrides.data", overrides.data);
|
||||
}
|
||||
assertArgument(overrides.to == null || (allowed || [ ]).indexOf("to") >= 0,
|
||||
"cannot override to", "overrides.to", overrides.to);
|
||||
assertArgument(overrides.data == null || (allowed || [ ]).indexOf("data") >= 0,
|
||||
"cannot override data", "overrides.data", overrides.data);
|
||||
|
||||
// Resolve any from
|
||||
if (overrides.from) {
|
||||
overrides.from = await resolveAddress(overrides.from);
|
||||
}
|
||||
|
||||
return <Omit<ContractTransaction, "data" | "to">>overrides;
|
||||
return <Omit<ContractTransaction, O>>overrides;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,6 +165,80 @@ export async function resolveArgs(_runner: null | ContractRunner, inputs: Readon
|
||||
}));
|
||||
}
|
||||
|
||||
class WrappedFallback {
|
||||
readonly _contract!: BaseContract;
|
||||
|
||||
constructor (contract: BaseContract) {
|
||||
defineProperties<WrappedFallback>(this, { _contract: contract });
|
||||
|
||||
const proxy = new Proxy(this, {
|
||||
// Perform send when called
|
||||
apply: async (target, thisArg, args: Array<any>) => {
|
||||
return await target.send(...args);
|
||||
},
|
||||
});
|
||||
|
||||
return proxy;
|
||||
}
|
||||
|
||||
async populateTransaction(overrides?: Omit<TransactionRequest, "to">): Promise<ContractTransaction> {
|
||||
// If an overrides was passed in, copy it and normalize the values
|
||||
|
||||
const tx: ContractTransaction = <any>(await copyOverrides<"data">(overrides, [ "data" ]));
|
||||
tx.to = await this._contract.getAddress();
|
||||
|
||||
const iface = this._contract.interface;
|
||||
|
||||
// Only allow payable contracts to set non-zero value
|
||||
const payable = iface.receive || (iface.fallback && iface.fallback.payable);
|
||||
assertArgument(payable || (tx.value || BN_0) === BN_0,
|
||||
"cannot send value to non-payable contract", "overrides.value", tx.value);
|
||||
|
||||
// Only allow fallback contracts to set non-empty data
|
||||
assertArgument(iface.fallback || (tx.data || "0x") === "0x",
|
||||
"cannot send data to receive-only contract", "overrides.data", tx.data);
|
||||
|
||||
return tx;
|
||||
}
|
||||
|
||||
async staticCall(overrides?: Omit<TransactionRequest, "to">): Promise<string> {
|
||||
const runner = getRunner(this._contract.runner, "call");
|
||||
assert(canCall(runner), "contract runner does not support calling",
|
||||
"UNSUPPORTED_OPERATION", { operation: "call" });
|
||||
|
||||
const tx = await this.populateTransaction(overrides);
|
||||
|
||||
try {
|
||||
return await runner.call(tx);
|
||||
} catch (error: any) {
|
||||
if (isCallException(error) && error.data) {
|
||||
throw this._contract.interface.makeError(error.data, tx);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async send(overrides?: Omit<TransactionRequest, "to">): Promise<ContractTransactionResponse> {
|
||||
const runner = this._contract.runner;
|
||||
assert(canSend(runner), "contract runner does not support sending transactions",
|
||||
"UNSUPPORTED_OPERATION", { operation: "sendTransaction" });
|
||||
|
||||
const tx = await runner.sendTransaction(await this.populateTransaction(overrides));
|
||||
const provider = getProvider(this._contract.runner);
|
||||
// @TODO: the provider can be null; make a custom dummy provider that will throw a
|
||||
// meaningful error
|
||||
return new ContractTransactionResponse(this._contract.interface, <Provider>provider, tx);
|
||||
}
|
||||
|
||||
async estimateGas(overrides?: Omit<TransactionRequest, "to">): Promise<bigint> {
|
||||
const runner = getRunner(this._contract.runner, "estimateGas");
|
||||
assert(canEstimate(runner), "contract runner does not support gas estimation",
|
||||
"UNSUPPORTED_OPERATION", { operation: "estimateGas" });
|
||||
|
||||
return await runner.estimateGas(await this.populateTransaction(overrides));
|
||||
}
|
||||
}
|
||||
|
||||
class WrappedMethod<A extends Array<any> = Array<any>, R = any, D extends R | ContractTransactionResponse = ContractTransactionResponse>
|
||||
extends _WrappedMethodBase() implements BaseContractMethod<A, R, D> {
|
||||
|
||||
@ -545,6 +618,8 @@ export class BaseContract implements Addressable, EventEmitterable<ContractEvent
|
||||
|
||||
readonly [internal]: any;
|
||||
|
||||
readonly fallback!: null | WrappedFallback;
|
||||
|
||||
constructor(target: string | Addressable, abi: Interface | InterfaceAbi, runner?: null | ContractRunner, _deployTx?: null | TransactionResponse) {
|
||||
if (runner == null) { runner = null; }
|
||||
const iface = Interface.from(abi);
|
||||
@ -614,6 +689,10 @@ export class BaseContract implements Addressable, EventEmitterable<ContractEvent
|
||||
});
|
||||
defineProperties<BaseContract>(this, { filters });
|
||||
|
||||
defineProperties<BaseContract>(this, {
|
||||
fallback: ((iface.receive || iface.fallback) ? (new WrappedFallback(this)): null)
|
||||
});
|
||||
|
||||
// Return a Proxy that will respond to functions
|
||||
return new Proxy(this, {
|
||||
get: (target, _prop, receiver) => {
|
||||
|
Loading…
Reference in New Issue
Block a user