-
Notifications
You must be signed in to change notification settings - Fork 113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: verification endpoint alignment with etherscan #367
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not import it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Import doesn't work because the semver package only functions with CommonJS modules. I found a workaround using require to properly use the object. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you try to add types? |
||
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<string, string> } = {}; | ||
for (let i = 1; i <= 10; i++) { | ||
|
@@ -166,21 +171,37 @@ 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`, { | ||
codeFormat: request.codeformat, | ||
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 | ||
: "0x", | ||
...(isSolidityContract && { | ||
compilerZksolcVersion: request.zkCompilerVersion, | ||
compilerZksolcVersion: request.zksolcVersion, | ||
compilerSolcVersion: request.compilerversion, | ||
}), | ||
...(!isSolidityContract && { | ||
compilerZkvyperVersion: request.zkCompilerVersion, | ||
compilerZkvyperVersion: request.zksolcVersion, | ||
compilerVyperVersion: request.compilerversion, | ||
}), | ||
}) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It shows that constructor arguments can be sent either with or without the 0x prefix, as the Etherscan API only accepts without the prefix. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then use |
||
required: false, | ||
}) | ||
@IsOptional() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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}`]; | ||
}, | ||
}); | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this mean that the change is breaking? If so, can we make it backward-compatible (e.g. add support for property alias)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this change is breaking. TBH I would better leave
zkCompilerVersion
for consistency and ask etherscan to rename on their side, because for solc it's notsolcVersion
, it'scompilerversion
. If we still decide to go withzksolcVersion
then we should support bothzksolcVersion
andzkCompilerVersion
to avoid breaking changes.