Here, “correct” means that both the token type and transferred amount are the same or equivalent to the token transferred on the source chain.
In the VAA, the token information is specified in the VAA payload by tokenChain and tokenAddress:
Note that the token is not necessarily a source chain or target chain token, but can be a token address on any chain. For example, Alice may transfer a USDC from Solana (tokenChain 1) to Polygon (tokenChain 5), and the token specified in the VAA could be USD Coin (USDC) on Ethereum (tokenAddress 0xa0b8, tokenChain 2).
To translate the token specified in the VAA to a correct token on the target chain, Wormhole has a feature called token attestation that allows users to register tokens. Say if Alice wants to register a new token on Solana which corresponds to an existing Token X on Ethereum, the process is as follows:
1. Alice can call the attestToken function on the Wormhole Token Bridge contract of the origin chain (Ethereum in this case) with the tokenAddress of Token X and a nonce:
The attestToken function will publish a message containing the token metadata, including tokenAddress, tokenChain (2 for Ethereum), token decimals, symbol and name:
2. The message will be observed by the guardians, which will then produce an attestation VAA, which can be retrieved using the sequence number returned by the attestToken function.
3. Alice can then submit the attestation VAA to every other chain (referred to as foreign chains for this token) by the CreateWrapped function of the Wormhole Token Bridge on the foreign chain (e.g., create_wrapped on Solana):
In the above, calling create_wrapped with the attestation VAA and a token mint will establish a correspondence between the attested token and the token mint.
Note that the token mint must be unique: two different attested tokens must correspond to different mints. In Solana, this is achieved by creating a PDA from the attested token metadata (chain, token_address, original_decimals):
The PDA is the mint address corresponding to the source token. When redeeming the token on Solana, the mint can be verified by verify_derivation on the PDA (wrapped_meta):
Because the mint address is PDA, only the Wormhole Token Bridge program has authority to mint the corresponding tokens. This ensures that anyone can call create_wrapped with the attestation VAA (if the mint has not been registered), but the attacker could not fake the mint.
If an attacker calls create_wrapped before Alice and supply a wrong mint, e.g., Dogecoin (D56d on Solana) registered instead of ETH — Ether (Portal) for WETH on Ethereum, then the check in accs.mint.verify_derivation(ctx.program_id, &derivation_data) will fail.
The process is similar on the other chains. For example, on Ethereum, the function createWrapped will deploy a new contract for the token (creating a Wormhole-Wrapped Token), and it invokes setWrappedAsset to update a map wrappedAsset storing (tokenChainId, tokenAddress) and a wrapped address wrapper:
When redeeming a token on Ethereum, the wrappedAsset map is then used to retrieve the corresponding token from tokenChainId and tokenAddress:
The complete transfer functions (CompleteNative and CompleteWrapped) both check the mint equality as shown below:
One caveat is that the recipient to provided by the user must be a valid token recipient. For example, on Solana, this means that the recipient must be a token account with the same mint as the PDA created in create_wrapped.
If (due to a user error) the recipient address is not a token account, or its token mint does not match the PDA with seeds (chain, token_address, original_decimals), then the user would never be able to redeem the token. The token could be locked on the bridge forever, unless the guardian network issues a new VAA with the correct recipient address.
The VAA contains the transfer amount uint256 amount, which ensures the amount of minted token on the target chain should be consistent with the amount. However, a technical problem here is that the bridged tokens may have different decimals. For instance, Ether has 18 decimals on Ethereum, but Wormhole limits token decimals to 8 (line 147 in the code below):
To address this problem, Wormhole must make sure the token decimals are correctly handled. If a token has more than 8 decimals (e.g., Ether), then Wormhole has to normalize its transferred amount wrt. to the 8 decimals limit.
More specifically, when encoding amount in the VAA payload, Wormhole denormalizes the transfer amount by calling deNormalizeAmount and deNormalizeAmount:
In the code above (lines 278 and 285), if decimals is larger than 8, the amount is shifted by decimals - 8 decimals (right and left respectively).
Note that due to the decimal shift, deposit dust will not be transferred to the target chain, and Wormhole will refund the dust (if any) to the sender on the source chain:
In the next article, we will continue to discuss how Wormhole avoids message double delivery, i.e., the VAA replay attacks.
sec3 is a security research firm that prepares Solana projects for millions of users. sec3’s Launch Audit is a rigorous, researcher-led code examination that investigates and certifies mainnet-grade smart contracts; sec3’s continuous auditing software platform, X-ray, integrates with Github to progressively scan pull requests, helping projects fortify code before deployment; and sec3’s post-deployment security solution, WatchTower, ensures funds stay safe. sec3 is building technology-based scalable solutions for Web3 projects to ensure protocols stay safe as they scale.
To learn more about sec3, please visit https://www.sec3.dev