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 { 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";
|
import type { ContractEventPayload, ContractEventName, Log } from "../index.js";
|
||||||
|
|
||||||
setupProviders();
|
setupProviders();
|
||||||
@ -22,7 +24,7 @@ describe("Test Contract", function() {
|
|||||||
"function testErrorString(bool pass, string calldata message) pure returns (uint256)",
|
"function testErrorString(bool pass, string calldata message) pure returns (uint256)",
|
||||||
"function testPanic(uint256 code) returns (uint256)",
|
"function testPanic(uint256 code) returns (uint256)",
|
||||||
"function testEvent(uint256 valueUint256, address valueAddress, string valueString, bytes valueBytes) public",
|
"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() {
|
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 = "constant external internal payable private public pure view";
|
||||||
const KwVisib = setify(_kwVisib.split(" "));
|
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 KwTypes = setify(_kwTypes.split(" "));
|
||||||
|
|
||||||
const _kwModifiers = "calldata memory storage payable indexed";
|
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
|
// ...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);
|
let modifiers = consumeKeywords(tokens, KwVisib);
|
||||||
|
|
||||||
// Detect conflicting modifiers
|
// Detect conflicting modifiers
|
||||||
@ -506,6 +506,7 @@ const ParamTypeInternal = "_ParamTypeInternal";
|
|||||||
const ErrorFragmentInternal = "_ErrorInternal";
|
const ErrorFragmentInternal = "_ErrorInternal";
|
||||||
const EventFragmentInternal = "_EventInternal";
|
const EventFragmentInternal = "_EventInternal";
|
||||||
const ConstructorFragmentInternal = "_ConstructorInternal";
|
const ConstructorFragmentInternal = "_ConstructorInternal";
|
||||||
|
const FallbackFragmentInternal = "_FallbackInternal";
|
||||||
const FunctionFragmentInternal = "_FunctionInternal";
|
const FunctionFragmentInternal = "_FunctionInternal";
|
||||||
const StructFragmentInternal = "_StructInternal";
|
const StructFragmentInternal = "_StructInternal";
|
||||||
|
|
||||||
@ -885,7 +886,7 @@ export class ParamType {
|
|||||||
/**
|
/**
|
||||||
* The type of a [[Fragment]].
|
* 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.
|
* 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 {
|
static from(obj: any): Fragment {
|
||||||
if (typeof(obj) === "string") {
|
if (typeof(obj) === "string") {
|
||||||
|
|
||||||
|
// Try parsing JSON...
|
||||||
try {
|
try {
|
||||||
Fragment.from(JSON.parse(obj));
|
Fragment.from(JSON.parse(obj));
|
||||||
} catch (e) { }
|
} catch (e) { }
|
||||||
|
|
||||||
|
// ...otherwise, use the human-readable lexer
|
||||||
return Fragment.from(lex(obj));
|
return Fragment.from(lex(obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (obj instanceof TokenString) {
|
if (obj instanceof TokenString) {
|
||||||
const type = obj.popKeyword(KwTypes);
|
// Human-readable ABI (already lexed)
|
||||||
|
|
||||||
|
const type = obj.peekKeyword(KwTypes);
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "constructor": return ConstructorFragment.from(obj);
|
case "constructor": return ConstructorFragment.from(obj);
|
||||||
case "error": return ErrorFragment.from(obj);
|
case "error": return ErrorFragment.from(obj);
|
||||||
case "event": return EventFragment.from(obj);
|
case "event": return EventFragment.from(obj);
|
||||||
|
case "fallback": case "receive":
|
||||||
|
return FallbackFragment.from(obj);
|
||||||
case "function": return FunctionFragment.from(obj);
|
case "function": return FunctionFragment.from(obj);
|
||||||
case "struct": return StructFragment.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) {
|
switch (obj.type) {
|
||||||
case "constructor": return ConstructorFragment.from(obj);
|
case "constructor": return ConstructorFragment.from(obj);
|
||||||
case "error": return ErrorFragment.from(obj);
|
case "error": return ErrorFragment.from(obj);
|
||||||
case "event": return EventFragment.from(obj);
|
case "event": return EventFragment.from(obj);
|
||||||
|
case "fallback": case "receive":
|
||||||
|
return FallbackFragment.from(obj);
|
||||||
case "function": return FunctionFragment.from(obj);
|
case "function": return FunctionFragment.from(obj);
|
||||||
case "struct": return StructFragment.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.
|
* A Fragment which represents a method.
|
||||||
*/
|
*/
|
||||||
@ -1220,10 +1327,10 @@ export class FunctionFragment extends NamedFragment {
|
|||||||
* The state mutability (e.g. ``payable``, ``nonpayable``, ``view``
|
* The state mutability (e.g. ``payable``, ``nonpayable``, ``view``
|
||||||
* or ``pure``)
|
* 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;
|
readonly payable!: boolean;
|
||||||
|
|
||||||
@ -1235,7 +1342,7 @@ export class FunctionFragment extends NamedFragment {
|
|||||||
/**
|
/**
|
||||||
* @private
|
* @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);
|
super(guard, "function", name, inputs);
|
||||||
Object.defineProperty(this, internal, { value: FunctionFragmentInternal });
|
Object.defineProperty(this, internal, { value: FunctionFragmentInternal });
|
||||||
outputs = Object.freeze(outputs.slice());
|
outputs = Object.freeze(outputs.slice());
|
||||||
|
@ -12,8 +12,8 @@ export { AbiCoder } from "./abi-coder.js";
|
|||||||
export { decodeBytes32String, encodeBytes32String } from "./bytes32.js";
|
export { decodeBytes32String, encodeBytes32String } from "./bytes32.js";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ConstructorFragment, ErrorFragment, EventFragment, Fragment,
|
ConstructorFragment, ErrorFragment, EventFragment, FallbackFragment,
|
||||||
FunctionFragment, NamedFragment, ParamType, StructFragment,
|
Fragment, FunctionFragment, NamedFragment, ParamType, StructFragment,
|
||||||
} from "./fragments.js";
|
} from "./fragments.js";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@ -14,7 +14,10 @@ import {
|
|||||||
|
|
||||||
import { AbiCoder } from "./abi-coder.js";
|
import { AbiCoder } from "./abi-coder.js";
|
||||||
import { checkResultErrors, Result } from "./coders/abstract-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 { Typed } from "./typed.js";
|
||||||
|
|
||||||
import type { BigNumberish, BytesLike, CallExceptionError, CallExceptionTransaction } from "../utils/index.js";
|
import type { BigNumberish, BytesLike, CallExceptionError, CallExceptionTransaction } from "../utils/index.js";
|
||||||
@ -175,6 +178,16 @@ export class Interface {
|
|||||||
*/
|
*/
|
||||||
readonly deploy!: ConstructorFragment;
|
readonly deploy!: ConstructorFragment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Fallback method, if any.
|
||||||
|
*/
|
||||||
|
readonly fallback!: null | FallbackFragment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If receiving ether is supported.
|
||||||
|
*/
|
||||||
|
readonly receive!: boolean;
|
||||||
|
|
||||||
#errors: Map<string, ErrorFragment>;
|
#errors: Map<string, ErrorFragment>;
|
||||||
#events: Map<string, EventFragment>;
|
#events: Map<string, EventFragment>;
|
||||||
#functions: Map<string, FunctionFragment>;
|
#functions: Map<string, FunctionFragment>;
|
||||||
@ -212,10 +225,13 @@ export class Interface {
|
|||||||
fragments: Object.freeze(frags)
|
fragments: Object.freeze(frags)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let fallback: null | FallbackFragment = null;
|
||||||
|
let receive = false;
|
||||||
|
|
||||||
this.#abiCoder = this.getAbiCoder();
|
this.#abiCoder = this.getAbiCoder();
|
||||||
|
|
||||||
// Add all fragments by their signature
|
// Add all fragments by their signature
|
||||||
this.fragments.forEach((fragment) => {
|
this.fragments.forEach((fragment, index) => {
|
||||||
let bucket: Map<string, Fragment>;
|
let bucket: Map<string, Fragment>;
|
||||||
switch (fragment.type) {
|
switch (fragment.type) {
|
||||||
case "constructor":
|
case "constructor":
|
||||||
@ -227,6 +243,17 @@ export class Interface {
|
|||||||
defineProperties<Interface>(this, { deploy: <ConstructorFragment>fragment });
|
defineProperties<Interface>(this, { deploy: <ConstructorFragment>fragment });
|
||||||
return;
|
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":
|
case "function":
|
||||||
//checkNames(fragment, "input", fragment.inputs);
|
//checkNames(fragment, "input", fragment.inputs);
|
||||||
//checkNames(fragment, "output", (<FunctionFragment>fragment).outputs);
|
//checkNames(fragment, "output", (<FunctionFragment>fragment).outputs);
|
||||||
@ -259,6 +286,8 @@ export class Interface {
|
|||||||
deploy: ConstructorFragment.from("constructor()")
|
deploy: ConstructorFragment.from("constructor()")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defineProperties<Interface>(this, { fallback, receive });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,6 +33,8 @@ import type {
|
|||||||
DeferredTopicFilter
|
DeferredTopicFilter
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
|
|
||||||
|
const BN_0 = BigInt(0);
|
||||||
|
|
||||||
interface ContractRunnerCaller extends ContractRunner {
|
interface ContractRunnerCaller extends ContractRunner {
|
||||||
call: (tx: TransactionRequest) => Promise<string>;
|
call: (tx: TransactionRequest) => Promise<string>;
|
||||||
}
|
}
|
||||||
@ -129,25 +131,22 @@ function getProvider(value: null | ContractRunner): null | Provider {
|
|||||||
/**
|
/**
|
||||||
* @_ignore:
|
* @_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)
|
// Create a shallow copy (we'll deep-ify anything needed during normalizing)
|
||||||
const overrides = copyRequest(Typed.dereference(arg, "overrides"));
|
const overrides = copyRequest(Typed.dereference(arg, "overrides"));
|
||||||
|
|
||||||
// Some sanity checking; these are what these methods adds
|
assertArgument(overrides.to == null || (allowed || [ ]).indexOf("to") >= 0,
|
||||||
//if ((<any>overrides).to) {
|
"cannot override to", "overrides.to", overrides.to);
|
||||||
if (overrides.to) {
|
assertArgument(overrides.data == null || (allowed || [ ]).indexOf("data") >= 0,
|
||||||
assertArgument(false, "cannot override to", "overrides.to", overrides.to);
|
"cannot override data", "overrides.data", overrides.data);
|
||||||
} else if (overrides.data) {
|
|
||||||
assertArgument(false, "cannot override data", "overrides.data", overrides.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve any from
|
// Resolve any from
|
||||||
if (overrides.from) {
|
if (overrides.from) {
|
||||||
overrides.from = await resolveAddress(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>
|
class WrappedMethod<A extends Array<any> = Array<any>, R = any, D extends R | ContractTransactionResponse = ContractTransactionResponse>
|
||||||
extends _WrappedMethodBase() implements BaseContractMethod<A, R, D> {
|
extends _WrappedMethodBase() implements BaseContractMethod<A, R, D> {
|
||||||
|
|
||||||
@ -545,6 +618,8 @@ export class BaseContract implements Addressable, EventEmitterable<ContractEvent
|
|||||||
|
|
||||||
readonly [internal]: any;
|
readonly [internal]: any;
|
||||||
|
|
||||||
|
readonly fallback!: null | WrappedFallback;
|
||||||
|
|
||||||
constructor(target: string | Addressable, abi: Interface | InterfaceAbi, runner?: null | ContractRunner, _deployTx?: null | TransactionResponse) {
|
constructor(target: string | Addressable, abi: Interface | InterfaceAbi, runner?: null | ContractRunner, _deployTx?: null | TransactionResponse) {
|
||||||
if (runner == null) { runner = null; }
|
if (runner == null) { runner = null; }
|
||||||
const iface = Interface.from(abi);
|
const iface = Interface.from(abi);
|
||||||
@ -614,6 +689,10 @@ export class BaseContract implements Addressable, EventEmitterable<ContractEvent
|
|||||||
});
|
});
|
||||||
defineProperties<BaseContract>(this, { filters });
|
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 a Proxy that will respond to functions
|
||||||
return new Proxy(this, {
|
return new Proxy(this, {
|
||||||
get: (target, _prop, receiver) => {
|
get: (target, _prop, receiver) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user