wormhole/ethereum/docs/token_verification.md

9.5 KiB

Token contract verification on Etherscan

This document explains how to perform verification of the wrapped asset contracts deployed by the Portal token bridge on Etherscan and similar explorer services. The purpose of contract verification is to provide the source code of the contract so that users have better transparency into the programs they interact with (whether this makes a difference in practice is beyond the scope of this document).

Verification requires uploading the source code and supplying the compiler version and arguments that the deployed bytecode was compiled with. Etherscan will then recompile the source code on their servers and match the result against the deployed bytecode.

We will use the flattened source file, as it's easier to upload. Flattened just means that the whole contract, including its dependencies, are inlined into a single source file. Wormhole's flattened source files can be downloaded from Github Actions. This page includes a version of the contracts for each ethereum contract deployment. The deployed token contract tends not to change very often (if at all), so it should be safe to just pick the latest run and download the artifacts from there (download flattened-contracts at the bottom of the page after selecting the run).

The file needed for token verification is token/Token.sol.

For our running example, we'll use the wrapped WETH contract deployed on Moonbeam: https://moonscan.io/address/0xab3f0245b83feb11d15aaffefd7ad465a59817ed

First, we verify that this contract is a proxy. This step can be done by visiting the https://moonbeam.moonscan.io/proxycontractchecker page, and pasting in the contract address. For ethereum, the site to use would be https://etherscan.io/proxycontractchecker, and other chain explorers have similar pages too.

This will present a popup with the following text:

The proxy contract verification completed with the message: The proxy's (0xab3f0245b83feb11d15aaffefd7ad465a59817ed) implementation contract is found at: 0xb1731c586ca89a23809861c6103f0b96b3f57d92

Click save.

Next, we'll verify the actual source code. Head over to https://moonscan.io/verifyContract and paste in contract address, in our case 0xab3f0245b83feb11d15aaffefd7ad465a59817ed. Fill in the rest of the form with the following values, then continue.

Field Value
Compiler type Solidity (Single file)
Compiler version v0.8.4+commit.c7e474f2
License type Apache-2

On the next page, select "optimizations: yes", and paste the contents of token/Token.sol from before into the source file textaera.

In the misc settings, enter 200 for optimization runs, and leave the rest as default.

ABI-encoded constructor arguments

The last missing piece is the ABI-encoded constructor arguments field. These are the arguments that the contract was instantiated with, and it will be different for each wrapped contract. There are two ways to proceed. The first method is easier, but does not work on all explorers (it does on moonscan), the second method is more involved, but will always work. Try the first, and if it didn't work, then try the second.

Constructor arguments, method #1

Leave the constructor arguments field empty, and just proceed to verification. This step will fail, because the deployed bytecode will actually include the constructor arguments, and the result of compiling the source file doesn't. At this point, some explorers will just return a generic error message saying the bytecodes didn't match, without any additional information. If this is the case, go to method #2.

If the page shows what the expected bytecode was, and lists out the actual bytecodes it found in the source file, then we may proceed here. The expected bytecode will be the same as the BridgeToken bytecode with the constructor arguments appended to the end. This means that the BridgeToken bytecode is a proper prefix of the expected bytecode. Just copy the rest of the bytes of the expected bytecode (for example in your favourite text editor), i.e. the suffix that comes after the BridgeToken prefix. Go back to the previous page, and use these bytes as the constructor arguments. This time, verification should succeed.

Constructor arguments, method #2

If the explorer page does not show the expected and actual bytecodes, then we need to compute the arguments ourselves. For this, we will need the forge tool (install from https://getfoundry.sh/), and a local checkout of the wormhole repository

$ git clone https://github.com/wormhole-foundation/wormhole.git

First, install the dependencies by running the make dependencies in the ethereum folder:

wormhole/ethereum $ make dependencies

Next, we will run a forge script to compute the constructor arguments. For this, we will need two pieces of data. First, the address of the Portal token bridge contract on the chain we're performing verification on. This can be found in https://github.com/wormhole-foundation/wormhole/blob/main/sdk/js/src/utils/consts.ts under the MAINNET section (or TESTNET if you're verifying a testnet contract). For our example, the mainnet moonbeam token bridge contract is 0xb1731c586ca89a23809861c6103f0b96b3f57d92.

Next, we'll need the VAA (the signed wormhole message) that was sent to the token bridge contract to create the wrapped asset. This can be found by finding the transaction that created this contract. On the contract's page https://moonscan.io/address/0xab3f0245b83feb11d15aaffefd7ad465a59817ed, the "Content creator" field has a link to the "at txn ...". In our case, this is https://moonscan.io/tx/0x9f6db0a0749558e8ef5bf24c1be148498fb3c451d040198b7362c2ce32469e67

Expand the details, which shows a call to createWrapped in the "Input data" section. Click "Decode input data" which will show the hex value of the encodedVM argument. In our case it's

0x01000000020d00aa6b30da219a0573e1a2ff47b4e51b4768ff671ac99b4ca851b69a5c756c495f5e5416b1bacd1e65e01085184a0ea167bb8160db15621e39b079b7d8cae26e71000207a7e7d1053e143dffa3c3023ca7ca8816bcfa62d264112d3b51ca4eadde5cb8615673252063c9bc327ba5eaeeef37fa8e08064822b6de8746065606de2749b4010349718c3a2ed771b885b972d01ee36e9267b142945e8d4af06c7b6d43f12830f5745f17e70eb368f510520cd5386a44dd8d5c6f875f8e28c40f8ff8e559400358000418b99fc0b8090b4ec7c6cde6c43f82dc9f1c15a7610624833287c963bf776c7641b1a8410acb4f1b927c3784dd9290aaa50fca818c728d33e42de9b16b3d3dba0005fb5fce6005369257755550e4e6f7b1023fab3f83e4e70ec822ade40571fb712b26e537b7394278151055048d85553c73c1c7dbb65a956c05ded272b7c480d0c001066732b0cd475f74d2afacee7ce175a44648f2cc0ffc88f07b29edd7bd6497b9c728bd7e2df8f4bd926e0faa2820874d2a9006e7b8bb7fefd4876ba7acebfb4271000738db4897b6c4054c4c724254d02c6dfe2486379d4ee8ff3ccc4e180395d798784ca1d35a219e34c31ae3defadc0765aca0e81b047f5748663164cf2060e33465010c03b1fd13f924d56238344d6a9de1d650120b106e93f5638b2eb23fa7706b9ecb1e2fa10c805e6ec4f24742c7e43fb815084dcb18720fb6eae90d59c1824acf64010d13c022cfea6df258340a44b7b5667da9f6c739d92b2f77351eeb0217faf244a84d50be71579b65850f92397deaa133e267f224ea2537c223a7c9966417843917000fdc31f1540f66fa5d2991b52af617be24eaa66f41ad5542ac0b4f972b0afafcec3e57f52e4e71b933fad4ec9cf69a4c4bdbbb1a2a9b44f07a287be493dcb1fdb50010904167e5728f1405aceaacb67832e23ebe374f6bda35f75e9bb79c21acbda8e50ad864f24a9fd26f0f6c1a4eba02506db0669a8cd9829245b40c4510aa0bd74f001104877f08d3917e176ac487b25e03d123121171716387a391b56053eaed9364b42becc817ef9973fdeb117e75dd665a7b22a7641c60fdb8786a50082a463702760112739bc4983ac0408cbc13969b7ba6715dfef337324d39b36f8120e883584f51726ea0f07a4e08d66dd5f1144d7dbb49720cd0547834ee58d5b918d242e956eb720163396ad38785000000020000000000000000000000003ee18b2214aff97000d974cf647e7c347e8fa5850000000000014c530102000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200021257455448000000000000000000000000000000000000000000000000000000005772617070656420457468657200000000000000000000000000000000000000

We copy this field and go back to our terminal. Run the following

wormhole/ethereum $ forge script scripts/TokenABI.s.sol -s "token_constructor_args(bytes, address)" <VAA-BYTES> <CONTRACT-ADDRESS>

where in place of <VAA-BYTES>, substitute the hex sequence we just copied from the explorer, and in place of <CONTRACT-ADDRESS>, the token bridge's address from before (0xb1731c586ca89a23809861c6103f0b96b3f57d92 for moonbeam).

Running that command prints the following:

0x000000000000000000000000b1731c586ca89a23809861c6103f0b96b3f57d9200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000164c71f461500000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000014c53000000000000000000000000b1731c586ca89a23809861c6103f0b96b3f57d920000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000d57726170706564204574686572000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004574554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

Copy the hex excluding the 0x at the front, and paste that into the constructor arguments field, then hit verify. The contract should now be verified.