From e441ba46f0fc056f26c23c3d22b24bac77f9be2b Mon Sep 17 00:00:00 2001 From: Phillip Ho Date: Fri, 1 Nov 2024 13:42:24 +0800 Subject: [PATCH] fix: Support missing 'function' prefix in functionName (#748) --- src/server/routes/contract/write/write.ts | 23 +++++++++++---- src/server/utils/abi.ts | 8 +++++- test/e2e/tests/{ => routes}/write.test.ts | 34 +++++++++++++++++++---- 3 files changed, 52 insertions(+), 13 deletions(-) rename test/e2e/tests/{ => routes}/write.test.ts (88%) diff --git a/src/server/routes/contract/write/write.ts b/src/server/routes/contract/write/write.ts index 892458bc..b4704978 100644 --- a/src/server/routes/contract/write/write.ts +++ b/src/server/routes/contract/write/write.ts @@ -1,8 +1,8 @@ -import { type Static, Type } from "@sinclair/typebox"; +import { Type, type Static } from "@sinclair/typebox"; import type { FastifyInstance } from "fastify"; import { StatusCodes } from "http-status-codes"; import { prepareContractCall, resolveMethod } from "thirdweb"; -import { type AbiFunction, parseAbiParams } from "thirdweb/utils"; +import { parseAbiParams, type AbiFunction } from "thirdweb/utils"; import { getContractV5 } from "../../../../utils/cache/getContractv5"; import { prettifyError } from "../../../../utils/error"; import { queueTransaction } from "../../../../utils/transaction/queueTransation"; @@ -19,17 +19,26 @@ import { requiredAddress, walletWithAAHeaderSchema, } from "../../../schemas/wallet"; -import { sanitizeAbi } from "../../../utils/abi"; +import { sanitizeAbi, sanitizeFunctionName } from "../../../utils/abi"; import { getChainIdFromChain } from "../../../utils/chain"; import { parseTransactionOverrides } from "../../../utils/transactionOverrides"; // INPUT const writeRequestBodySchema = Type.Object({ functionName: Type.String({ - description: "The function to call on the contract", + description: `The function to call on the contract. It is highly recommended to provide a full function signature, such as "function mintTo(address to, uint256 amount)", to avoid ambiguity and to skip ABI resolution.`, + examples: ["function mintTo(address to, uint256 amount)"], }), args: Type.Array(Type.Any(), { - description: "The arguments to call on the function", + description: + "An array of arguments to provide the function. Supports: numbers, strings, arrays, objects. Do not provide: BigNumber, bigint, Date objects", + examples: [ + [ + 1730380951, + "0x09530565aC1Ce08C3621f5B24Fca6d9a76574620", + ["a", "b", "c"], + ], + ], }), ...txOverridesWithValueSchema.properties, abi: Type.Optional(abiArraySchema), @@ -85,7 +94,8 @@ export async function writeToContract(fastify: FastifyInstance) { let method: AbiFunction; let params: Array; try { - method = await resolveMethod(functionName)(contract); + const functionNameOrSignature = sanitizeFunctionName(functionName); + method = await resolveMethod(functionNameOrSignature)(contract); params = parseAbiParams( method.inputs.map((i) => i.type), args, @@ -97,6 +107,7 @@ export async function writeToContract(fastify: FastifyInstance) { "BAD_REQUEST", ); } + const transaction = prepareContractCall({ contract, method, diff --git a/src/server/utils/abi.ts b/src/server/utils/abi.ts index 2edd27be..cedb5041 100644 --- a/src/server/utils/abi.ts +++ b/src/server/utils/abi.ts @@ -2,7 +2,10 @@ import type { Abi } from "thirdweb/utils"; import type { AbiSchemaType } from "../schemas/contract"; export function sanitizeAbi(abi: AbiSchemaType | undefined): Abi | undefined { - if (!abi) return undefined; + if (!abi) { + return undefined; + } + return abi.map((item) => { if (item.type === "function") { return { @@ -15,3 +18,6 @@ export function sanitizeAbi(abi: AbiSchemaType | undefined): Abi | undefined { return item; }) as Abi; } + +export const sanitizeFunctionName = (val: string) => + val.includes("(") && !val.startsWith("function ") ? `function ${val}` : val; diff --git a/test/e2e/tests/write.test.ts b/test/e2e/tests/routes/write.test.ts similarity index 88% rename from test/e2e/tests/write.test.ts rename to test/e2e/tests/routes/write.test.ts index c49e45ac..1cad9a98 100644 --- a/test/e2e/tests/write.test.ts +++ b/test/e2e/tests/routes/write.test.ts @@ -2,13 +2,13 @@ import { beforeAll, describe, expect, test } from "bun:test"; import assert from "node:assert"; import { type Address, stringToHex } from "thirdweb"; import { zeroAddress } from "viem"; -import type { ApiError } from "../../../sdk/dist/thirdweb-dev-engine.cjs"; -import { CONFIG } from "../config"; -import type { setupEngine } from "../utils/engine"; -import { pollTransactionStatus } from "../utils/transactions"; -import { setup } from "./setup"; +import type { ApiError } from "../../../../sdk/dist/thirdweb-dev-engine.cjs.js"; +import { CONFIG } from "../../config"; +import type { setupEngine } from "../../utils/engine"; +import { pollTransactionStatus } from "../../utils/transactions"; +import { setup } from "../setup"; -describe("Write Tests", () => { +describe("/contract/write route", () => { let tokenContractAddress: string; let engine: ReturnType; let backendWallet: Address; @@ -183,6 +183,28 @@ describe("Write Tests", () => { expect(writeTransactionStatus.minedAt).toBeDefined(); }); + test("Write to a contract with function signature without prefix", async () => { + const writeRes = await engine.contract.write( + CONFIG.CHAIN.id.toString(), + tokenContractAddress, + backendWallet, + { + functionName: "setContractURI(string uri)", + args: ["https://signature-test.com"], + }, + ); + + expect(writeRes.result.queueId).toBeDefined(); + + const writeTransactionStatus = await pollTransactionStatus( + engine, + writeRes.result.queueId, + true, + ); + + expect(writeTransactionStatus.minedAt).toBeDefined(); + }); + test("Write to a contract with function abi", async () => { const writeRes = await engine.contract.write( CONFIG.CHAIN.id.toString(),