Skip to content

Commit

Permalink
feat: display partial & automatic verification info (#383)
Browse files Browse the repository at this point in the history
Adds visuals for automatic verification and partial matching introduced
in matter-labs/zksync-era#3527

Demo:

<details>
<summary>Unverified contract (no changes from previous
behavior)</summary>


![image](https://github.com/user-attachments/assets/d0872c9d-c9ec-4e00-afef-25e1e9e6a1d4)
</details>
<details>
<summary>Fully verified contract (no changes from previous
behavior)</summary>


![image](https://github.com/user-attachments/assets/68f90165-46af-44ac-8ce8-b595fb2a6450)
</details>
<details>
  <summary>Automatically verified contract</summary>


![image](https://github.com/user-attachments/assets/3536d7be-5da7-4364-b5e7-a32ba184afb1)
</details>
<details>
<summary>Partially verified contract (metadata hash mismatch)</summary>


![image](https://github.com/user-attachments/assets/7cdb4daa-9b59-433b-9a15-f5d833c15e3f)
</details>
<details>
<summary>Partially verified contract + automatic verification (e.g.
there is a verified contract with the same bytecode but different
metadata hash)</summary>


![image](https://github.com/user-attachments/assets/e1883ede-9fbd-474a-8bc9-401b2e050f06)
</details>

Unfortunately, since this feature depends on contract verifier version
that isn't deployed anywhere yet, it's not easy to test it.
If you want to ~~suffer~~ reproduce it locally, you can do it as
follows:

<details>
  <summary>How to test locally</summary>

1. Use `zksync-era` repo on the most recent `main` & install `zkstack`
CLI.
2. `zkstack e init --dev`
3. `zkstack contract-verifier init --zksolc-version=v1.5.7
--zkvyper-version=v1.5.4 --solc-version=0.8.26 --vyper-version=v0.3.10
--era-vm-solc-version=0.8.26-1.0.1 --only`
4. Run `zkstack explorer init`
5. Run `zkstack server --components
api,tree,eth,state_keeper,housekeeper,commitment_generator,da_dispatcher,vm_runner_protective_reads,contract_verification_api`
in one terminal
6. Run `zkstack contract-verifier run` in another terminal
7. Run `zkstack explorer run-backend` in third terminal
8. Configure block explorer FE to interact with dockerized explorer BE
and contract verifier: change `apiUrl` port to `3002` and add
`"verificationApiUrl": "http://127.0.0.1:3070"`
9. Run block explorer FE (only)
10. Use `npx zksync-cli bridge deposit`, choose dockerized setup, PK for
rich wallet is
`0xbe79721778b48bcc679b78edac0ce48306a8578186ffcb9f2ee455ae6efeace1`
(address `0xe706e60ab5Dc512C36A4646D719b889F398cbBcB`)
11. Do `export COMPILERS=/path/to/zksync-era/etc`
12. Go to `zksync-era/core/tests/ts-integration/contracts/counter`
13. Do `$COMPILERS/zksolc-bin/v1.5.7/zksolc --solc
$COMPILERS/solc-bin/zkVM-0.8.28-1.0.1/solc counter.sol --bin`
14. Deploy the bytecode with the script shown below
15. Open tx in the explorer, find the log with three topics, one of
which is deployer address; the third one will contain the address of the
deployed contract.
16. Verify the contract through UI

Now you can do the following:
- To check automatic verification, just deploy the same contract once
again. Verification will not be needed
- To check partial matching, change the last byte (you will corrupt the
metadata hash, and it will trigger partial verification)

Script
```
import { ethers, hexlify, solidityPacked } from "ethers";
import { Wallet, Provider } from "zksync-ethers";
import { CONTRACT_DEPLOYER_ADDRESS, hashBytecode } from "zksync-ethers/build/utils";

const PK = "0xbe79721778b48bcc679b78edac0ce48306a8578186ffcb9f2ee455ae6efeace1";
const BYTECODE = "0x<insert bytecode here>";

async function main() {
    // Create deploy transaction using zksync-ethers using bytecode
    // and send it to the network
    const provider = new Provider("http://localhost:3050");
    const wallet = new Wallet(PK, provider);

    const bytecodeHash = hashBytecode(BYTECODE);
    // const contract = new ethers.Contract(CONTRACT_DEPLOYER_ADDRESS, abi, wallet);
    const selector = "create(bytes32,bytes32,bytes)";
    const selectorHash = ethers.keccak256(ethers.toUtf8Bytes(selector)).substring(0, 10);
    console.log(`Selector hash: ${selectorHash}`);
    const calldata = selectorHash + solidityPacked(["bytes32", "bytes32", "bytes"], [new Uint8Array(32), bytecodeHash, new Uint8Array(64)]).substring(2);
    console.log(`Calldata: ${calldata}`);

    const bytecode_u8_array = ethers.getBytes(BYTECODE);
    const tx = await wallet.sendTransaction({
        to: CONTRACT_DEPLOYER_ADDRESS,
        data: calldata,
        value: 0,
        customData: {
            factoryDeps: [bytecode_u8_array],
        }
    });
    console.log(`Transaction hash: ${tx.hash}`);
    await tx.wait();
}

main()
    .then(() => process.exit(0))
    .catch((err) => {
        console.error('Error:', err.message || err);
        process.exit(1);
    });
```

</details>

Disclaimer: I don't know front-end, I hate front-end, I question myself
why I thought it's a good idea to work on this.

![i_have_no_idea_what_im_doing_meme_640_07](https://github.com/user-attachments/assets/27f7053d-42b3-4ece-a69b-631c3e28e84c)

P.S. I wanted to add Ukrainian translation as well, but looks like
contract verification has no translations, so I thought that it deserves
a dedicated effort to add all the missing translations when/if it would
be required.
  • Loading branch information
popzxc authored Feb 7, 2025
1 parent de4fda4 commit 2f896cc
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 13 deletions.
42 changes: 42 additions & 0 deletions packages/app/src/components/contract/CompilationInfo.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
<template>
<div class="verification-alert-container" v-if="autoVerified">
<Alert type="notification">
{{ t("contractVerification.compilationInfo.autoVerified") }}
<AddressLink :address="verificationRequest!.contractAddress">{{
shortValue(verificationRequest!.contractAddress)
}}</AddressLink>
</Alert>
</div>
<div class="verification-alert-container" v-if="partialVerification">
<Alert type="warning">
{{ t("contractVerification.compilationInfo.partialVerification") }}
<a :href="PARTIAL_VERIFICATION_DETAILS_URL">{{
t("contractVerification.compilationInfo.partialVerificationDetails")
}}</a>
</Alert>
</div>

<div v-if="partialVerification || autoVerified">
<VerificationButton :address="contract.address" />
</div>
<div class="label-container">
<div>
<p class="label">{{ t("contractVerification.compilationInfo.contractName") }}</p>
Expand Down Expand Up @@ -30,9 +50,16 @@
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import AddressLink from "../AddressLink.vue";
import Alert from "@/components/common/Alert.vue";
import VerificationButton from "@/components/contract/VerificationButton.vue";
import type { Contract } from "@/composables/useAddress";
import type { PropType } from "vue";
import { shortValue } from "@/utils/formatters";
const { t } = useI18n();
const props = defineProps({
Expand All @@ -45,6 +72,18 @@ const props = defineProps({
const verificationRequest = computed(() => props.contract.verificationInfo?.request);
const optimization = computed(() => (props.contract.verificationInfo?.request.optimizationUsed ? "Yes" : "No"));
const contractName = computed(() => props.contract.verificationInfo?.request.contractName.replace(/.*\.(sol|vy):/, ""));
// If address of the contract doesn't match address in the request, the contract was verified "automatically".
const autoVerified = computed(
() => props.contract.address.toLowerCase() !== props.contract.verificationInfo?.request.contractAddress.toLowerCase()
);
// If there are any problems with the verification, the contract is only partially verified.
const partialVerification = computed(() => {
const problems = props.contract.verificationInfo?.verificationProblems;
return problems ? problems.length > 0 : false;
});
const PARTIAL_VERIFICATION_DETAILS_URL =
"https://ethereum.org/en/developers/docs/smart-contracts/verifying/#full-verification";
</script>
<style lang="scss" scoped>
.label-container {
Expand All @@ -56,4 +95,7 @@ const contractName = computed(() => props.contract.verificationInfo?.request.con
.text {
@apply max-w-[16rem] break-all;
}
.verification-alert-container {
@apply w-full mb-2;
}
</style>
14 changes: 2 additions & 12 deletions packages/app/src/components/contract/ContractBytecode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,7 @@
</div>
</div>
<div class="contract-link-container">
<Button
class="contract-verification-link"
:data-testid="$testId.contractVerificationButton"
tag="RouterLink"
:to="{ name: 'contract-verification', query: { address: contract.address } }"
>
{{ t("contract.bytecode.verifyButton") }}
</Button>
<VerificationButton :address="contract.address" />
</div>
</div>
<div v-else class="functions-contract-container">
Expand Down Expand Up @@ -47,11 +40,11 @@
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import Button from "@/components/common/Button.vue";
import AbiData from "@/components/common/table/fields/AbiData.vue";
import ByteData from "@/components/common/table/fields/ByteData.vue";
import CodeBlock from "@/components/contract/CodeBlock.vue";
import CompilationInfo from "@/components/contract/CompilationInfo.vue";
import VerificationButton from "@/components/contract/VerificationButton.vue";
import type { Contract } from "@/composables/useAddress";
import type { PropType } from "vue";
Expand Down Expand Up @@ -129,9 +122,6 @@ const abiJson = computed<undefined | string>(() => {
}
.contract-link-container {
@apply mt-5 flex items-end md:mt-0;
.contract-verification-link {
@apply whitespace-nowrap md:px-5 md:py-3;
}
}
}
.functions-contract-container {
Expand Down
35 changes: 35 additions & 0 deletions packages/app/src/components/contract/VerificationButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<template>
<Button
class="contract-verification-link"
:data-testid="$testId.contractVerificationButton"
tag="RouterLink"
:to="{ name: 'contract-verification', query: { address } }"
>
{{ t("contract.bytecode.verifyButton") }}
</Button>
</template>

<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import Button from "@/components/common/Button.vue";
import type { Address } from "@/types";
import type { PropType } from "vue";
defineProps({
address: {
type: Object as PropType<Address>,
default: () => ({}),
required: true,
},
});
const { t } = useI18n();
</script>

<style>
.contract-verification-link {
@apply whitespace-nowrap md:px-5 md:py-3;
}
</style>
3 changes: 3 additions & 0 deletions packages/app/src/composables/useAddress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,16 @@ type ContractVerificationRequest = {
optimizationUsed: boolean;
};

export const VERIFICATION_PROBLEM_INCORRECT_METADATA = "incorrectMetadata";

export type ContractVerificationInfo = {
artifacts: {
abi: AbiFragment[];
bytecode: number[];
};
request: ContractVerificationRequest;
verifiedAt: string;
verificationProblems?: string[];
};

export type Balance = Api.Response.TokenAddress;
Expand Down
5 changes: 4 additions & 1 deletion packages/app/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,10 @@
"compilerVersion": "Compiler Version",
"zksolcVersion": "Zksolc Version",
"zkvyperVersion": "Zkvyper Version",
"optimization": "Optimization"
"optimization": "Optimization",
"autoVerified": "The contract wasn't manually verified, but its bytecode matches the bytecode of the following contract:",
"partialVerification": "Contract is partially verified: the contract metadata hash and/or constructor arguments do not match. Partial verification provides less guarantees about the authenticity of the code.",
"partialVerificationDetails": "See more details"
},
"multiFileVerification": {
"solcTitle": "Please select the Solidity (*.sol) files for upload",
Expand Down

0 comments on commit 2f896cc

Please sign in to comment.