Skip to content

Commit

Permalink
error parsing for estimateGas
Browse files Browse the repository at this point in the history
  • Loading branch information
joaquim-verges committed Feb 15, 2024
1 parent 9e8a119 commit a625920
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 39 deletions.
2 changes: 1 addition & 1 deletion packages/thirdweb/src/extensions/erc721/write/mintTo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export type MintToParams = {
*/
export function mintTo(options: BaseTransactionOptions<MintToParams>) {
return prepareContractCall({
...options,
contract: options.contract,
method: "function mintTo(address _to, string memory _tokenURI)",
params: async () => {
let tokenUri: string;
Expand Down
5 changes: 4 additions & 1 deletion packages/thirdweb/src/transaction/actions/encode.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Hex } from "viem";
import type { PreparedTransaction } from "../prepare-transaction.js";
import type { Abi, AbiFunction } from "abitype";

/**
* Encodes a transaction object into a hexadecimal string representation of the encoded data.
Expand All @@ -12,7 +13,9 @@ import type { PreparedTransaction } from "../prepare-transaction.js";
* const encodedData = await encode(transaction);
* ```
*/
export async function encode(transaction: PreparedTransaction): Promise<Hex> {
export async function encode<abi extends Abi, abiFn extends AbiFunction>(
transaction: PreparedTransaction<abi, abiFn>,
): Promise<Hex> {
if (transaction.data === undefined) {
return "0x";
}
Expand Down
33 changes: 24 additions & 9 deletions packages/thirdweb/src/transaction/actions/estimate-gas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { formatTransactionRequest } from "viem/utils";
import type { Account } from "../../wallets/interfaces/wallet.js";
import { resolvePromisedValue } from "../../utils/promise/resolve-promised-value.js";
import type { PreparedTransaction } from "../prepare-transaction.js";
import { extractError as parseEstimationError } from "../extract-error.js";

export type EstimateGasOptions = {
transaction: PreparedTransaction;
Expand Down Expand Up @@ -41,7 +42,14 @@ export async function estimateGas(

// if the account itself overrides the estimateGas function, use that
if (options.account && options.account.estimateGas) {
return await options.account.estimateGas(options.transaction);
try {
return await options.account.estimateGas(options.transaction);
} catch (error) {
throw await parseEstimationError({
error,
contract: options.transaction.__contract,
});
}
}

// load up encode function if we need it
Expand All @@ -58,14 +66,21 @@ export async function estimateGas(
]);

const rpcRequest = getRpcClient(options.transaction);
return await eth_estimateGas(
rpcRequest,
formatTransactionRequest({
to: toAddress,
data: encodedData,
from: options.account?.address ?? undefined,
}),
);
try {
return await eth_estimateGas(
rpcRequest,
formatTransactionRequest({
to: toAddress,
data: encodedData,
from: options.account?.address ?? undefined,
}),
);
} catch (error) {
throw await parseEstimationError({
error,
contract: options.transaction.__contract,
});
}
})();
cache.set(options.transaction, promise);
return promise;
Expand Down
13 changes: 7 additions & 6 deletions packages/thirdweb/src/transaction/actions/simulate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import type { Account } from "../../wallets/interfaces/wallet.js";
import { resolvePromisedValue } from "../../utils/promise/resolve-promised-value.js";
import type { PreparedTransaction } from "../prepare-transaction.js";
import { eth_call } from "../../rpc/index.js";
import type { AbiFunction } from "abitype";
import type { Abi, AbiFunction } from "abitype";
import type { ReadContractResult } from "../read-contract.js";
import { decodeFunctionResult } from "../../abi/decode.js";

export type SimulateOptions<abiFn extends AbiFunction> = {
transaction: PreparedTransaction<abiFn>;
export type SimulateOptions<abi extends Abi, abiFn extends AbiFunction> = {
transaction: PreparedTransaction<abi, abiFn>;
account?: Partial<Account> | undefined;
};

Expand All @@ -25,9 +25,10 @@ export type SimulateOptions<abiFn extends AbiFunction> = {
* });
* ```
*/
export async function simulateTransaction<const abiFn extends AbiFunction>(
options: SimulateOptions<abiFn>,
) {
export async function simulateTransaction<
const abi extends Abi,
const abiFn extends AbiFunction,
>(options: SimulateOptions<abi, abiFn>) {
const { getRpcClient } = await import("../../rpc/index.js");
const rpcRequest = getRpcClient(options.transaction);

Expand Down
33 changes: 33 additions & 0 deletions packages/thirdweb/src/transaction/extract-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { decodeErrorResult, type Hex } from "viem";
import { resolveContractAbi } from "../contract/actions/resolve-abi.js";
import type { ThirdwebContract } from "../index.js";

/**
* @internal
*/
export async function extractError(args: {
error: unknown;
contract?: ThirdwebContract;
}) {
const { error, contract } = args;
if (typeof error === "object") {
// try to parse RPC error
const errorObj = error as {
message?: string;
code?: number;
data?: Hex;
};
if (errorObj.data) {
const parsedError = decodeErrorResult({
data: errorObj.data,
abi: contract ? await resolveContractAbi(contract) : undefined,
});
return new Error(
`Execution reverted: ${parsedError.errorName} - ${parsedError.args.join(
",",
)}`,
);
}
}
return error;
}
1 change: 1 addition & 0 deletions packages/thirdweb/src/transaction/prepare-contract-call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ export function prepareContractCall<
},
{
abi: resolveAbiFunction_,
contract: contract,
},
);
}
30 changes: 22 additions & 8 deletions packages/thirdweb/src/transaction/prepare-transaction.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { AccessList, Hex } from "viem";
import type { AbiFunction, Address } from "abitype";
import type { Abi, AbiFunction, Address } from "abitype";
import type { Chain } from "../chain/index.js";
import type { ThirdwebClient } from "../client/client.js";
import type { PromisedValue } from "../utils/promise/resolve-promised-value.js";
import type { ThirdwebContract } from "../contract/contract.js";

export type PrepareTransactionOptions = {
accessList?: PromisedValue<AccessList | undefined>;
Expand All @@ -20,14 +21,21 @@ export type PrepareTransactionOptions = {
client: ThirdwebClient;
};

type Additional<abiFn extends AbiFunction = AbiFunction> = {
type Additional<
abi extends Abi = [],
abiFn extends AbiFunction = AbiFunction,
> = {
abi: () => Promise<abiFn>;
contract: ThirdwebContract<abi>;
};

export type PreparedTransaction<abiFn extends AbiFunction = AbiFunction> =
Readonly<PrepareTransactionOptions> & {
__abi?: () => Promise<abiFn>;
};
export type PreparedTransaction<
abi extends Abi = [],
abiFn extends AbiFunction = AbiFunction,
> = Readonly<PrepareTransactionOptions> & {
__abi?: () => Promise<abiFn>;
__contract?: ThirdwebContract<abi>;
};

/**
* Prepares a transaction with the given options.
Expand All @@ -47,7 +55,13 @@ export type PreparedTransaction<abiFn extends AbiFunction = AbiFunction> =
* ```
*/
export function prepareTransaction<
const abi extends Abi = [],
const abiFn extends AbiFunction = AbiFunction,
>(options: PrepareTransactionOptions, info?: Additional<abiFn>) {
return { ...options, __abi: info?.abi } as PreparedTransaction<abiFn>;
>(options: PrepareTransactionOptions, info?: Additional<abi, abiFn>) {
console.log("prepareTransaction", options);
return {
...options,
__abi: info?.abi,
__contract: info?.contract,
} as PreparedTransaction<abi, abiFn>;
}
2 changes: 1 addition & 1 deletion packages/thirdweb/src/wallets/injected/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,8 @@ export class InjectedWallet implements Wallet {
method: "eth_sendTransaction",
params: [
{
gas: tx.gas ? toHex(tx.gas) : undefined,
value: tx.value ? toHex(tx.value) : undefined,
gas: tx.gas ? toHex(tx.gas) : undefined,
from: this.address,
to: tx.to as Address,
data: tx.data,
Expand Down
20 changes: 7 additions & 13 deletions packages/thirdweb/src/wallets/smart/lib/receipts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { decodeAbiParameters, type Hex } from "viem";
import { decodeErrorResult, type Hex } from "viem";
import type { Chain } from "../../../chain/index.js";
import type { ThirdwebClient } from "../../../client/client.js";
import { getContract } from "../../../contract/contract.js";
Expand Down Expand Up @@ -30,7 +30,6 @@ export async function getUserOpEventFromEntrypoint(args: {
// actually only want *this* userOpHash, so we can filter here
filters: {
userOpHash: userOpHash as Hex,
// TODO: @joaquim can we filter by sender and paymaster, too?
},
});
const events = await getContractEvents({
Expand All @@ -42,27 +41,22 @@ export async function getUserOpEventFromEntrypoint(args: {
// no longe need to `find` here because we already filter in the getContractEvents() call above
const event = events[0];
// UserOp can revert, so we need to surface revert reason
if (event?.args.success === false) {
if (event && event.args.success === false) {
const revertOpEvent = prepareEvent({
signature:
"event UserOperationRevertReasonEvent(bytes32 indexed userOpHash, address indexed sender, uint256 nonce, bytes revertReason)",
});
const revertEvent = await getContractEvents({
contract: entryPointContract,
events: [revertOpEvent],
fromBlock: event?.blockNumber,
toBlock: event?.blockNumber,
blockHash: event.blockHash,
});
const firstRevertEvent = revertEvent[0];
if (firstRevertEvent) {
let message: string = firstRevertEvent.args.revertReason;
if (message.startsWith("0x08c379a0")) {
message = decodeAbiParameters(
[{ type: "string" }],
`0x${message.substring(10)}`,
)[0];
}
throw new Error(`UserOp failed with reason: ${message}`);
const message = decodeErrorResult({
data: firstRevertEvent.args.revertReason,
});
throw new Error(`UserOp failed with reason: ${message.args.join(",")}`);
} else {
throw new Error("UserOp failed with unknown reason");
}
Expand Down

0 comments on commit a625920

Please sign in to comment.