Skip to content

Commit

Permalink
Implement hardhat API request for verifyContractABI (#250)
Browse files Browse the repository at this point in the history
### TL;DR
Added support for zkSync network contract verification using ABI-based verification method.

### What changed?
- Introduced a new `VerificationService` class to handle ABI-based contract verification
- Added zkSync network detection (chainIds: 300, 324, 37111)
- Moved error handling logic to a separate file
- Created new error types for username/project validation
- Implemented ABI verification flow for zkSync contracts

### How to test?
1. Configure your Tenderly credentials in `hardhat.config.js`:
```javascript
{
  tenderly: {
    username: "your-username",
    project: "your-project"
  }
}
```
2. Deploy a contract to zkSync network
3. Verify the contract using:
```javascript
await hre.tenderly.verify({
  address: "deployed-contract-address",
  name: "ContractName"
});
```

### Why make this change?
zkSync networks require a different verification approach due to their unique architecture. This change enables contract verification on zkSync networks by implementing an ABI-based verification method, ensuring compatibility with Tenderly's verification system.
  • Loading branch information
dule-git authored Nov 21, 2024
1 parent 994b4ed commit 31fd155
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 18 deletions.
50 changes: 32 additions & 18 deletions packages/tenderly-hardhat/src/Tenderly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
NO_COMPILER_FOUND_FOR_CONTRACT_ERR_MSG,
} from "./tenderly/errors";
import {
extractCompilerVersion,
extractCompilerVersion, getChainId,
getCompilerDataFromContracts,
getContracts,
isTenderlyNetworkConfig,
Expand All @@ -28,24 +28,39 @@ import {
import { DEFAULT_CHAIN_ID, PLUGIN_NAME, VERIFICATION_TYPES } from "./constants";
import { TenderlyNetwork } from "./TenderlyNetwork";
import { ProxyPlaceholderName } from "./index";
import { VerificationService } from "./verification";
import { throwIfUsernameOrProjectNotSet, UndefinedChainIdError } from "./errors";

export class Tenderly {
public env: HardhatRuntimeEnvironment;
public tenderlyNetwork: TenderlyNetwork;

private tenderlyService = new TenderlyService(PLUGIN_NAME);
private readonly verificationService;

constructor(hre: HardhatRuntimeEnvironment) {
logger.debug("Creating Tenderly plugin.");

this.env = hre;
this.tenderlyNetwork = new TenderlyNetwork(hre);
this.verificationService = new VerificationService(this.tenderlyService);

logger.debug("Created Tenderly plugin.");
}

public async verify(...contracts: any[]): Promise<void> {
logger.info("Verification invoked.");

if (await this._isZkSyncNetwork(this.env)) {
for (let contract of contracts) {
contract = contract as ContractByName;
this.verificationService.verifyContractABI(
this.env,
contract.address,
contract.name,
);
}
}

// If there are proxy contracts, we can run the task without further processing.
const proxyContracts = contracts.filter(
Expand Down Expand Up @@ -96,7 +111,7 @@ export class Tenderly {
logger.info(
`Network parameter is set to '${this.getNetworkName()}', redirecting to ${verificationType} verification.`,
);
await this._throwIfUsernameOrProjectNotSet();
await throwIfUsernameOrProjectNotSet(this.env);

return this.tenderlyNetwork.verify(requestData);
}
Expand All @@ -105,7 +120,7 @@ export class Tenderly {
logger.info(
"Private verification flag is set to true, redirecting to private verification.",
);
await this._throwIfUsernameOrProjectNotSet();
await throwIfUsernameOrProjectNotSet(this.env);

return this.tenderlyService.pushContractsMultiCompiler(
requestData,
Expand Down Expand Up @@ -164,6 +179,18 @@ export class Tenderly {
break;
}
}

private async _isZkSyncNetwork(hre: HardhatRuntimeEnvironment): Promise<boolean> {
let chainId;
try {
chainId = await getChainId(hre)
} catch(e) {
if (e instanceof UndefinedChainIdError) {}
else throw e;
}

return chainId === 300 || chainId === 324 || chainId === 37111
}

public async verifyForkMultiCompilerAPI(
request: TenderlyVerifyContractsRequest,
Expand All @@ -180,7 +207,7 @@ export class Tenderly {
);
return;
}
await this._throwIfUsernameOrProjectNotSet();
await throwIfUsernameOrProjectNotSet(this.env);

await this.tenderlyNetwork.verifyMultiCompilerAPI(
request,
Expand All @@ -205,7 +232,7 @@ export class Tenderly {
);
return;
}
await this._throwIfUsernameOrProjectNotSet();
await throwIfUsernameOrProjectNotSet(this.env);

await this.tenderlyNetwork.verifyDevnetMultiCompilerAPI(
request,
Expand Down Expand Up @@ -253,19 +280,6 @@ export class Tenderly {
return this.verify(...contracts);
}

private async _throwIfUsernameOrProjectNotSet(): Promise<void> {
if (this.env.config.tenderly?.project === undefined) {
throw Error(
`Error in ${PLUGIN_NAME}: Please provide the project field in the tenderly object in hardhat.config.js`,
);
}
if (this.env.config.tenderly?.username === undefined) {
throw Error(
`Error in ${PLUGIN_NAME}: Please provide the username field in the tenderly object in hardhat.config.js`,
);
}
}

public async verifyAPI(
request: TenderlyContractUploadRequest,
): Promise<void> {
Expand Down
22 changes: 22 additions & 0 deletions packages/tenderly-hardhat/src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
import { PLUGIN_NAME } from "./constants";
import { HardhatRuntimeEnvironment } from "hardhat/types";

export class UndefinedChainIdError extends Error {
constructor(networkName: string) {
super(`Couldn't find chainId for the network: ${networkName}. \nPlease provide the chainId in the network config object`);
}
}

export class UsernameOrProjectNotProvidedError extends Error {
constructor() {
super(`Please provide the username and project fields in the tenderly object in hardhat.config.js`);
}
}


export async function throwIfUsernameOrProjectNotSet(
hre: HardhatRuntimeEnvironment,
): Promise<void> {
if (hre.config.tenderly?.username === undefined) {
throw new UsernameOrProjectNotProvidedError();
}
if (hre.config.tenderly?.project === undefined) {
throw new UsernameOrProjectNotProvidedError();
}
}

1 change: 1 addition & 0 deletions packages/tenderly-hardhat/src/verification/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { VerificationService } from "./service"
46 changes: 46 additions & 0 deletions packages/tenderly-hardhat/src/verification/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { TenderlyService } from "@tenderly/api-client";
import {
VerifyContractABIRequest,
VerifyContractABIResponse
} from "@tenderly/api-client";
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { throwIfUsernameOrProjectNotSet } from "../errors";
import { getChainId } from "../utils/util";

export class VerificationService {
private readonly tenderlyService: TenderlyService;

constructor(
tenderlyService: TenderlyService
) {
this.tenderlyService = tenderlyService;
}

public async verifyContractABI(
hre: HardhatRuntimeEnvironment,
address: string,
contractName: string,
): Promise<VerifyContractABIResponse> {
await throwIfUsernameOrProjectNotSet(hre);

const networkId = await getChainId(hre);
const abi = (await hre.artifacts.readArtifact(contractName)).abi;
const abiString = JSON.stringify(abi);

const request: VerifyContractABIRequest = {
networkId: networkId.toString(),
address: address,
contractName: contractName,
abi: abiString,
}

const username = hre.config.tenderly.username;
const project = hre.config.tenderly.project;

return await this.tenderlyService.verifyContractABI(
username,
project,
request,
)
}
}

0 comments on commit 31fd155

Please sign in to comment.