From 448493400c0e9a13bb492b53b3c534d011908238 Mon Sep 17 00:00:00 2001 From: Marko Arambasic Date: Tue, 24 Dec 2024 12:48:58 +0100 Subject: [PATCH 1/2] fix: verification endpoint alignment with etherscan --- .../api/contract/contract.controller.spec.ts | 6 +-- .../src/api/contract/contract.controller.ts | 31 ++++++++++--- .../contract/verifyContractRequest.dto.ts | 14 +++--- .../formatAndValidateCompilerVersion.spec.ts | 35 +++++++++++++++ .../formatAndValidateCompilerVersion.ts | 43 +++++++++++++++++++ 5 files changed, 115 insertions(+), 14 deletions(-) create mode 100644 packages/api/src/common/decorators/formatAndValidateCompilerVersion.spec.ts create mode 100644 packages/api/src/common/decorators/formatAndValidateCompilerVersion.ts diff --git a/packages/api/src/api/contract/contract.controller.spec.ts b/packages/api/src/api/contract/contract.controller.spec.ts index 47761405e8..f2dae4cc0c 100644 --- a/packages/api/src/api/contract/contract.controller.spec.ts +++ b/packages/api/src/api/contract/contract.controller.spec.ts @@ -278,7 +278,7 @@ describe("ContractController", () => { contractname: "contracts/HelloWorld.sol:HelloWorld", compilerversion: "0.8.17", optimizationUsed: "1", - zkCompilerVersion: "v1.3.14", + zksolcVersion: "v1.3.14", constructorArguements: "0x94869207468657265210000000000000000000000000000000000000000000000", runs: 700, libraryname1: "contracts/MiniMath.sol:MiniMath", @@ -426,7 +426,7 @@ describe("ContractController", () => { contractname: "contracts/Greeter.vy:Greeter", compilerversion: "0.3.3", optimizationUsed: "1", - zkCompilerVersion: "v1.3.11", + zksolcVersion: "v1.3.11", } as unknown as VerifyContractRequestDto; pipeMock.mockReturnValue( @@ -442,7 +442,7 @@ describe("ContractController", () => { codeFormat: "vyper-multi-file", compilerVyperVersion: "0.3.3", compilerZkvyperVersion: "v1.3.11", - constructorArguments: undefined, + constructorArguments: null, contractAddress: "0x14174c76E073f8efEf5C1FE0dd0f8c2Ca9F21e62", contractName: "contracts/Greeter.vy:Greeter", optimizationUsed: true, diff --git a/packages/api/src/api/contract/contract.controller.ts b/packages/api/src/api/contract/contract.controller.ts index 699ffe0467..8381e6a184 100644 --- a/packages/api/src/api/contract/contract.controller.ts +++ b/packages/api/src/api/contract/contract.controller.ts @@ -32,7 +32,6 @@ import { ContractVerificationStatusResponse, } from "../types"; import { VerifyContractResponseDto } from "../dtos/contract/verifyContractResponse.dto"; - const entityName = "contract"; export const parseAddressListPipeExceptionFactory = () => new BadRequestException("Missing contract addresses"); @@ -136,6 +135,12 @@ export class ContractController { ContractVerificationCodeFormatEnum.solidityJsonInput, ].includes(request.codeformat); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const semver = require("semver"); + if (semver.gte(request.zksolcVersion, "1.3.23")) { + request.compilerversion = `zkVM-${request.compilerversion}-1.0.1`; + } + if (isSolidityContract && request.sourceCode instanceof Object) { const libraries: { [key: string]: Record } = {}; for (let i = 1; i <= 10; i++) { @@ -166,6 +171,18 @@ export class ContractController { } } + let formatedStringSourceCode = undefined; + if (isSolidityContract && typeof request.sourceCode === "string") { + try { + formatedStringSourceCode = JSON.parse(request.sourceCode); + if (formatedStringSourceCode.settings.optimizer?.enabled) { + request.optimizationUsed = "1"; + } + } catch (e) { + formatedStringSourceCode = request.sourceCode; + } + } + const { data } = await firstValueFrom<{ data: number }>( this.httpService .post(`${this.contractVerificationApiUrl}/contract_verification`, { @@ -173,14 +190,18 @@ export class ContractController { contractAddress, contractName: request.contractname, optimizationUsed: request.optimizationUsed === "1", - sourceCode: request.sourceCode, - constructorArguments: request.constructorArguements, + sourceCode: typeof request.sourceCode === "string" ? formatedStringSourceCode : request.sourceCode, + constructorArguments: request.constructorArguements + ? request.constructorArguements.slice(0, 2) !== "0x" + ? `0x${request.constructorArguements}` + : request.constructorArguements + : null, ...(isSolidityContract && { - compilerZksolcVersion: request.zkCompilerVersion, + compilerZksolcVersion: request.zksolcVersion, compilerSolcVersion: request.compilerversion, }), ...(!isSolidityContract && { - compilerZkvyperVersion: request.zkCompilerVersion, + compilerZkvyperVersion: request.zksolcVersion, compilerVyperVersion: request.compilerversion, }), }) diff --git a/packages/api/src/api/dtos/contract/verifyContractRequest.dto.ts b/packages/api/src/api/dtos/contract/verifyContractRequest.dto.ts index ab25d00703..17e5f25bd3 100644 --- a/packages/api/src/api/dtos/contract/verifyContractRequest.dto.ts +++ b/packages/api/src/api/dtos/contract/verifyContractRequest.dto.ts @@ -2,6 +2,7 @@ import { IsInt, IsOptional, Max, Min, IsEnum, IsString, IsNotEmpty, Matches } fr import { ApiProperty } from "@nestjs/swagger"; import { Type } from "class-transformer"; import { ContractVerificationCodeFormatEnum } from "../../types"; +import { FormatAndValidateCompilerVersion } from "../../../common/decorators/formatAndValidateCompilerVersion"; const fullLibraryNameRegexp = new RegExp("^(.)+:(.)+$"); @@ -86,17 +87,18 @@ export class VerifyContractRequestDto { }) @IsString() @IsNotEmpty({ message: "Missing Or invalid compilerversion." }) + @FormatAndValidateCompilerVersion({ message: "Invalid compilerversion format." }) public compilerversion: string; @ApiProperty({ - name: "zkCompilerVersion", + name: "zksolcVersion", description: "Zk compiler version", example: "v1.3.14", required: true, }) @IsString() - @IsNotEmpty({ message: "Missing zkCompilerVersion" }) - public zkCompilerVersion: string; + @IsNotEmpty({ message: "Missing zksolcVersion" }) + public zksolcVersion: string; @ApiProperty({ name: "runs", @@ -115,19 +117,19 @@ export class VerifyContractRequestDto { name: "optimizationUsed", description: "0 = No Optimization, 1 = Optimization used", example: "1", - required: true, + required: false, }) @IsEnum(["0", "1"], { message: "Invalid optimizationUsed", }) - @IsNotEmpty({ message: "Missing optimizationUsed" }) + @IsOptional() public optimizationUsed: string; @ApiProperty({ name: "constructorArguements", description: "Contract constructor arguments", example: - "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000094869207468657265210000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000094869207468657265210000000000000000000000000000000000000000000000 or 000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000094869207468657265210000000000000000000000000000000000000000000000", required: false, }) @IsOptional() diff --git a/packages/api/src/common/decorators/formatAndValidateCompilerVersion.spec.ts b/packages/api/src/common/decorators/formatAndValidateCompilerVersion.spec.ts new file mode 100644 index 0000000000..5820c8b349 --- /dev/null +++ b/packages/api/src/common/decorators/formatAndValidateCompilerVersion.spec.ts @@ -0,0 +1,35 @@ +import { validate } from "class-validator"; +import { FormatAndValidateCompilerVersion } from "./formatAndValidateCompilerVersion"; + +class TestDto { + constructor(version: string) { + this.version = version; + } + + @FormatAndValidateCompilerVersion() + public version: string; +} + +describe("FormatAndValidateCompilerVersion", () => { + it("when version is null returns a validation error", async () => { + const errors = await validate(new TestDto(null)); + expect(errors.length).toBe(1); + expect(errors[0].property).toBe("version"); + }); + + it("when version is an empty string returns a validation error", async () => { + const errors = await validate(new TestDto("")); + expect(errors.length).toBe(1); + expect(errors[0].property).toBe("version"); + }); + + it("when version is a valid", async () => { + const errors = await validate(new TestDto("2.3.7")); + expect(errors.length).toBe(0); + }); + + it("when version is valid with commit", async () => { + const errors = await validate(new TestDto("2.5.7-commit.32")); + expect(errors.length).toBe(0); + }); +}); diff --git a/packages/api/src/common/decorators/formatAndValidateCompilerVersion.ts b/packages/api/src/common/decorators/formatAndValidateCompilerVersion.ts new file mode 100644 index 0000000000..6772683324 --- /dev/null +++ b/packages/api/src/common/decorators/formatAndValidateCompilerVersion.ts @@ -0,0 +1,43 @@ +import { registerDecorator, ValidationOptions } from "class-validator"; +export function FormatAndValidateCompilerVersion(validationOptions?: ValidationOptions) { + return function (object: any, propertyName: string) { + registerDecorator({ + name: "formatAndValidateCompilerVersion", + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + validator: { + validate(value: any) { + return value && typeof value === "string"; + }, + }, + }); + // Custom setter to format the value + Object.defineProperty(object, propertyName, { + set(value: string) { + const regex = /^(0\.\d+\.\d+(\.\d+)?|zkVM-\d+\.\d+\.\d+(\.\d+)?-\d+\.\d+\.\d+(\.\d+)?)$/; + if (value && !regex.test(value)) { + let [major, minor, patch] = value.split("."); + major = major.slice(1); + patch = patch.replace(/\+.*$/, ""); + minor = minor; + const formattedValue = `${major}.${minor}.${patch}`; + Object.defineProperty(object, `_${propertyName}`, { + value: formattedValue, + writable: true, + configurable: true, + }); + } else { + Object.defineProperty(object, `_${propertyName}`, { + value: value, + writable: true, + configurable: true, + }); + } + }, + get() { + return this[`_${propertyName}`]; + }, + }); + }; +} From e3f982f02f1fa04b3b2b5a320bbfda1a5887f2d8 Mon Sep 17 00:00:00 2001 From: Marko Arambasic Date: Wed, 25 Dec 2024 09:59:04 +0100 Subject: [PATCH 2/2] fix: resove empty constructor arguments properly --- packages/api/src/api/contract/contract.controller.spec.ts | 2 +- packages/api/src/api/contract/contract.controller.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/api/src/api/contract/contract.controller.spec.ts b/packages/api/src/api/contract/contract.controller.spec.ts index f2dae4cc0c..9b5dbe7f8f 100644 --- a/packages/api/src/api/contract/contract.controller.spec.ts +++ b/packages/api/src/api/contract/contract.controller.spec.ts @@ -442,7 +442,7 @@ describe("ContractController", () => { codeFormat: "vyper-multi-file", compilerVyperVersion: "0.3.3", compilerZkvyperVersion: "v1.3.11", - constructorArguments: null, + constructorArguments: "0x", contractAddress: "0x14174c76E073f8efEf5C1FE0dd0f8c2Ca9F21e62", contractName: "contracts/Greeter.vy:Greeter", optimizationUsed: true, diff --git a/packages/api/src/api/contract/contract.controller.ts b/packages/api/src/api/contract/contract.controller.ts index 8381e6a184..91283cf3aa 100644 --- a/packages/api/src/api/contract/contract.controller.ts +++ b/packages/api/src/api/contract/contract.controller.ts @@ -195,7 +195,7 @@ export class ContractController { ? request.constructorArguements.slice(0, 2) !== "0x" ? `0x${request.constructorArguements}` : request.constructorArguements - : null, + : "0x", ...(isSolidityContract && { compilerZksolcVersion: request.zksolcVersion, compilerSolcVersion: request.compilerversion,