From ef5487186221252d5ddce92baf0e1d0275e8a8c2 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Thu, 29 Feb 2024 14:51:38 +0100 Subject: [PATCH] Finalize deposit flow Here we implement the finalization of the deposit flow. This process is started on the `L1BitcoinDepositor` side once the tBTC `Bridge` completes minting of the TBTC token. The process consists of multiple steps: 1. The `L1BitcoinDepositor` marks the given deposit as finalized and determines the amount of TBTC minted. 2. The `L1BitcoinDepositor` initiates a Wormhole token transfer. This transfer locks minted TBTC on L1 within the Wormhole Token Bridge and unlocks Wormhole-wrapped L2 TBTC for the `L2WormholeGateway` contract. 3. The `L2BitcoinDepositor` sends the transfer VAA to `L2BitcoinDepositor`. 4. The `L2BitcoinDepositor` finalizes the transfer by calling the `L2WormholeGateway` that redeems Wormhole-wrapped L2 TBTC from the Wormhole Token Bridge and uses it to mint canonical L2 TBTC for the L2 deposit owner. --- solidity/contracts/l2/L1BitcoinDepositor.sol | 183 ++++++++++++++---- solidity/contracts/l2/L2BitcoinDepositor.sol | 77 +++++--- solidity/contracts/l2/L2WormholeGateway.sol | 60 +----- solidity/contracts/l2/Wormhole.sol | 170 ++++++++++++++++ .../contracts/test/WormholeBridgeStub.sol | 2 +- 5 files changed, 370 insertions(+), 122 deletions(-) create mode 100644 solidity/contracts/l2/Wormhole.sol diff --git a/solidity/contracts/l2/L1BitcoinDepositor.sol b/solidity/contracts/l2/L1BitcoinDepositor.sol index acccf6011..fefb0b5d8 100644 --- a/solidity/contracts/l2/L1BitcoinDepositor.sol +++ b/solidity/contracts/l2/L1BitcoinDepositor.sol @@ -15,25 +15,14 @@ pragma solidity ^0.8.17; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "../integrator/AbstractTBTCDepositor.sol"; import "../integrator/IBridge.sol"; - -/// @title IWormholeReceiver -/// @notice Wormhole Receiver interface. Contains only selected functions -/// used by L1BitcoinDepositor. -/// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormholeReceiver.sol#L8 -interface IWormholeReceiver { - /// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormholeReceiver.sol#L44 - function receiveWormholeMessages( - bytes memory payload, - bytes[] memory additionalMessages, - bytes32 sourceAddress, - uint16 sourceChain, - bytes32 deliveryHash - ) external payable; -} +import "../integrator/ITBTCVault.sol"; +import "./Wormhole.sol"; // TODO: Document this contract. contract L1BitcoinDepositor is @@ -41,6 +30,8 @@ contract L1BitcoinDepositor is IWormholeReceiver, OwnableUpgradeable { + using SafeERC20 for IERC20; + /// @notice Reflects the deposit state: /// - Unknown deposit has not been initialized yet. /// - Initialized deposit has been initialized with a call to @@ -60,10 +51,14 @@ contract L1BitcoinDepositor is mapping(uint256 => DepositState) public deposits; // TODO: Document other state variables. - address public wormholeRelayer; - address public wormholeTokenBridge; + IERC20 public tbtcToken; + IWormhole public wormhole; + IWormholeRelayer public wormholeRelayer; + IWormholeTokenBridge public wormholeTokenBridge; + address public l2WormholeGateway; uint16 public l2ChainId; address public l2BitcoinDepositor; + uint256 public l2FinalizeDepositGasLimit; event DepositInitialized( uint256 indexed depositKey, @@ -71,6 +66,14 @@ contract L1BitcoinDepositor is address indexed l1Sender ); + event DepositFinalized( + uint256 indexed depositKey, + uint256 initialAmount, + uint256 tbtcAmount + ); + + event L2FinalizeDepositGasLimitUpdated(uint256 l2FinalizeDepositGasLimit); + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); @@ -79,18 +82,33 @@ contract L1BitcoinDepositor is function initialize( address _tbtcBridge, address _tbtcVault, + address _wormhole, address _wormholeRelayer, address _wormholeTokenBridge, + address _l2WormholeGateway, uint16 _l2ChainId, address _l2BitcoinDepositor ) external initializer { __AbstractTBTCDepositor_initialize(_tbtcBridge, _tbtcVault); __Ownable_init(); - wormholeRelayer = _wormholeRelayer; - wormholeTokenBridge = _wormholeTokenBridge; + tbtcToken = IERC20(ITBTCVault(_tbtcVault).tbtcToken()); + wormhole = IWormhole(_wormhole); + wormholeRelayer = IWormholeRelayer(_wormholeRelayer); + wormholeTokenBridge = IWormholeTokenBridge(_wormholeTokenBridge); + l2WormholeGateway = _l2WormholeGateway; l2ChainId = _l2ChainId; l2BitcoinDepositor = _l2BitcoinDepositor; + l2FinalizeDepositGasLimit = 200_000; + } + + // TODO: Document this function. + function updateL2FinalizeDepositGasLimit(uint256 _l2FinalizeDepositGasLimit) + external + onlyOwner + { + l2FinalizeDepositGasLimit = _l2FinalizeDepositGasLimit; + emit L2FinalizeDepositGasLimitUpdated(_l2FinalizeDepositGasLimit); } // TODO: Document this function. @@ -112,7 +130,8 @@ contract L1BitcoinDepositor is ); require( - fromWormholeAddress(sourceAddress) == l2BitcoinDepositor, + WormholeUtils.fromWormholeAddress(sourceAddress) == + l2BitcoinDepositor, "Source address is not the expected L2 Bitcoin depositor" ); @@ -143,11 +162,15 @@ contract L1BitcoinDepositor is "L2 deposit owner must not be 0x0" ); + // Convert the L2 deposit owner address into the Wormhole format and + // encode it as deposit extra data. + bytes32 extraData = WormholeUtils.toWormholeAddress(l2DepositOwner); + // TODO: Document how the Bridge works and why we don't need to validate input parameters. (uint256 depositKey, ) = _initializeDeposit( fundingTx, reveal, - toWormholeAddress(l2DepositOwner) + extraData ); require( @@ -160,23 +183,113 @@ contract L1BitcoinDepositor is emit DepositInitialized(depositKey, l2DepositOwner, msg.sender); } - /// @notice Converts Ethereum address into Wormhole format. - /// @param _address The address to convert. - function toWormholeAddress(address _address) - internal - pure - returns (bytes32) - { - return bytes32(uint256(uint160(_address))); + // TODO: Document this function. + function finalizeDeposit(uint256 depositKey) external payable { + require( + deposits[depositKey] == DepositState.Initialized, + "Wrong deposit state" + ); + + deposits[depositKey] = DepositState.Finalized; + + ( + uint256 initialDepositAmount, + uint256 tbtcAmount, + // Deposit extra data is actually the L2 deposit owner + // address in Wormhole format. + bytes32 l2DepositOwner + ) = _finalizeDeposit(depositKey); + + emit DepositFinalized(depositKey, initialDepositAmount, tbtcAmount); + + transferTbtc(tbtcAmount, l2DepositOwner); + } + + // TODO: Document this function. + function quoteFinalizeDeposit() public view returns (uint256 cost) { + cost = _quoteFinalizeDeposit(wormhole.messageFee()); } - /// @notice Converts Wormhole address into Ethereum format. - /// @param _address The address to convert. - function fromWormholeAddress(bytes32 _address) + // TODO: Document this function. + function _quoteFinalizeDeposit(uint256 messageFee) internal - pure - returns (address) + view + returns (uint256 cost) { - return address(uint160(uint256(_address))); + // Cost of delivering token and payload to `l2ChainId`. + (uint256 deliveryCost, ) = wormholeRelayer.quoteEVMDeliveryPrice( + l2ChainId, + 0, + l2FinalizeDepositGasLimit + ); + + // Total cost = delivery cost + cost of publishing the `sending token` + // Wormhole message. + cost = deliveryCost + messageFee; + } + + // TODO: Document this function. + function transferTbtc(uint256 amount, bytes32 l2Receiver) internal { + // Wormhole supports the 1e8 precision at most. TBTC is 1e18 so + // the amount needs to be normalized. + amount = WormholeUtils.normalize(amount); + + require(amount > 0, "Amount too low to bridge"); + + // Cost of requesting a `finalize` message to be sent to + // `l2ChainId` with a gasLimit of `l2FinalizeDepositGasLimit`. + uint256 wormholeMessageFee = wormhole.messageFee(); + uint256 cost = _quoteFinalizeDeposit(wormholeMessageFee); + + require(msg.value == cost, "Payment for Wormhole Relayer is too low"); + + // The Wormhole Token Bridge will pull the TBTC amount + // from this contract. We need to approve the transfer first. + tbtcToken.safeIncreaseAllowance(address(wormholeTokenBridge), amount); + + // Initiate a Wormhole token transfer that will lock L1 TBTC within + // the Wormhole Token Bridge contract and transfer Wormhole-wrapped + // L2 TBTC to the corresponding `L2WormholeGateway` contract. + uint64 transferSequence = wormholeTokenBridge.transferTokensWithPayload{ + value: wormholeMessageFee + }( + address(tbtcToken), + amount, + l2ChainId, + WormholeUtils.toWormholeAddress(l2WormholeGateway), + 0, // Nonce is a free field that is not relevant in this context. + abi.encode(l2Receiver) // Set the L2 receiver address as the transfer payload. + ); + + // Get Wormhole chain ID for this L1 chain. + uint16 l1ChainId = wormhole.chainId(); + + // Construct VAA representing the above Wormhole token transfer. + WormholeTypes.VaaKey[] + memory additionalVaas = new WormholeTypes.VaaKey[](1); + additionalVaas[0] = WormholeTypes.VaaKey({ + chainId: l1ChainId, + emitterAddress: WormholeUtils.toWormholeAddress( + address(wormholeTokenBridge) + ), + sequence: transferSequence + }); + + // The Wormhole token transfer initiated above must be finalized on + // the L2 chain. We achieve that by sending the transfer VAA to the + // `L2BitcoinDepositor` contract. Once, the `L2BitcoinDepositor` + // contract receives it, it calls the `L2WormholeGateway` contract + // that redeems Wormhole-wrapped L2 TBTC from the Wormhole Token + // Bridge and use it to mint canonical L2 TBTC to the receiver address. + wormholeRelayer.sendVaasToEvm{value: cost - wormholeMessageFee}( + l2ChainId, + l2BitcoinDepositor, + bytes(""), // No payload needed. The L2 receiver address is already encoded in the Wormhole token transfer payload. + 0, // No receiver value needed. + l2FinalizeDepositGasLimit, + additionalVaas, + l1ChainId, // Set this L1 chain as the refund chain. + msg.sender // Set the caller as the refund receiver. + ); } } diff --git a/solidity/contracts/l2/L2BitcoinDepositor.sol b/solidity/contracts/l2/L2BitcoinDepositor.sol index 24f5f47d9..afec86e4d 100644 --- a/solidity/contracts/l2/L2BitcoinDepositor.sol +++ b/solidity/contracts/l2/L2BitcoinDepositor.sol @@ -19,43 +19,21 @@ import {BTCUtils} from "@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "../integrator/IBridge.sol"; +import "./Wormhole.sol"; -/// @title IWormholeRelayer -/// @notice Wormhole Relayer interface. Contains only selected functions -/// used by L2BitcoinDepositor. -/// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormholeRelayer.sol#L74 -interface IWormholeRelayer { - /// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormholeRelayer.sol#L122 - function sendPayloadToEvm( - uint16 targetChain, - address targetAddress, - bytes memory payload, - uint256 receiverValue, - uint256 gasLimit, - uint16 refundChain, - address refundAddress - ) external payable returns (uint64 sequence); - - /// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormholeRelayer.sol#L442 - function quoteEVMDeliveryPrice( - uint16 targetChain, - uint256 receiverValue, - uint256 gasLimit - ) - external - view - returns ( - uint256 nativePriceQuote, - uint256 targetChainRefundPerGasUnused - ); +// TODO: Document this interface. +interface IL2WormholeGateway { + // TODO: Document this function. + function receiveTbtc(bytes memory vaa) external; } // TODO: Document this contract. -contract L2BitcoinDepositor is OwnableUpgradeable { +contract L2BitcoinDepositor is IWormholeReceiver, OwnableUpgradeable { using BTCUtils for bytes; // TODO: Document state variables. IWormholeRelayer public wormholeRelayer; + IL2WormholeGateway public l2WormholeGateway; uint16 public l2ChainId; uint16 public l1ChainId; address public l1BitcoinDepositor; @@ -79,6 +57,7 @@ contract L2BitcoinDepositor is OwnableUpgradeable { function initialize( address _wormholeRelayer, + address _l2WormholeGateway, uint16 _l2ChainId, uint16 _l1ChainId, address _l1BitcoinDepositor @@ -86,6 +65,7 @@ contract L2BitcoinDepositor is OwnableUpgradeable { __Ownable_init(); wormholeRelayer = IWormholeRelayer(_wormholeRelayer); + l2WormholeGateway = IL2WormholeGateway(_l2WormholeGateway); l2ChainId = _l2ChainId; l1ChainId = _l1ChainId; l1BitcoinDepositor = _l1BitcoinDepositor; @@ -112,7 +92,7 @@ contract L2BitcoinDepositor is OwnableUpgradeable { ); // Cost of requesting a `initializeDeposit` message to be sent to - // `l1Chain` with a gasLimit of `l1InitializeDepositGasLimit`. + // `l1ChainId` with a gasLimit of `l1InitializeDepositGasLimit`. uint256 cost = quoteInitializeDeposit(); require(msg.value == cost, "Payment for Wormhole Relayer is too low"); @@ -161,4 +141,41 @@ contract L2BitcoinDepositor is OwnableUpgradeable { l1InitializeDepositGasLimit ); } + + // TODO: Document this function. + function receiveWormholeMessages( + bytes memory, + bytes[] memory additionalVaas, + bytes32 sourceAddress, + uint16 sourceChain, + bytes32 + ) external payable { + require( + msg.sender == address(wormholeRelayer), + "Caller is not Wormhole Relayer" + ); + + require( + sourceChain == l1ChainId, + "Source chain is not the expected L1 chain" + ); + + require( + WormholeUtils.fromWormholeAddress(sourceAddress) == + l1BitcoinDepositor, + "Source address is not the expected L1 Bitcoin depositor" + ); + + require( + additionalVaas.length == 1, + "Expected 1 additional VAA key for token transfer" + ); + + finalizeDeposit(additionalVaas[0]); + } + + // TODO: Document this function. + function finalizeDeposit(bytes memory vaa) internal { + l2WormholeGateway.receiveTbtc(vaa); + } } diff --git a/solidity/contracts/l2/L2WormholeGateway.sol b/solidity/contracts/l2/L2WormholeGateway.sol index 88fec4514..8b2dfa2a4 100644 --- a/solidity/contracts/l2/L2WormholeGateway.sol +++ b/solidity/contracts/l2/L2WormholeGateway.sol @@ -20,51 +20,9 @@ import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable. import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import "./Wormhole.sol"; import "./L2TBTC.sol"; -/// @title IWormholeTokenBridge -/// @notice Wormhole Token Bridge interface. Contains only selected functions -/// used by L2WormholeGateway. -interface IWormholeTokenBridge { - function completeTransferWithPayload(bytes memory encodedVm) - external - returns (bytes memory); - - function parseTransferWithPayload(bytes memory encoded) - external - pure - returns (TransferWithPayload memory transfer); - - function transferTokens( - address token, - uint256 amount, - uint16 recipientChain, - bytes32 recipient, - uint256 arbiterFee, - uint32 nonce - ) external payable returns (uint64 sequence); - - function transferTokensWithPayload( - address token, - uint256 amount, - uint16 recipientChain, - bytes32 recipient, - uint32 nonce, - bytes memory payload - ) external payable returns (uint64 sequence); - - struct TransferWithPayload { - uint8 payloadID; - uint256 amount; - bytes32 tokenAddress; - uint16 tokenChain; - bytes32 to; - uint16 toChain; - bytes32 fromAddress; - bytes payload; - } -} - /// @title L2WormholeGateway /// @notice Selected cross-ecosystem bridges are given the minting authority for /// tBTC token on L2 and sidechains. This contract gives a minting @@ -216,7 +174,7 @@ contract L2WormholeGateway is // Normalize the amount to bridge. The dust can not be bridged due to // the decimal shift in the Wormhole Bridge contract. - amount = normalize(amount); + amount = WormholeUtils.normalize(amount); // Check again after dropping the dust. require(amount != 0, "Amount too low to bridge"); @@ -362,7 +320,7 @@ contract L2WormholeGateway is pure returns (bytes32) { - return bytes32(uint256(uint160(_address))); + return WormholeUtils.toWormholeAddress(_address); } /// @notice Converts Wormhole address into Ethereum format. @@ -372,16 +330,6 @@ contract L2WormholeGateway is pure returns (address) { - return address(uint160(uint256(_address))); - } - - /// @dev Eliminates the dust that cannot be bridged with Wormhole - /// due to the decimal shift in the Wormhole Bridge contract. - /// See https://github.com/wormhole-foundation/wormhole/blob/96682bdbeb7c87bfa110eade0554b3d8cbf788d2/ethereum/contracts/bridge/Bridge.sol#L276-L288 - function normalize(uint256 amount) internal pure returns (uint256) { - // slither-disable-next-line divide-before-multiply - amount /= 10**10; - amount *= 10**10; - return amount; + return WormholeUtils.fromWormholeAddress(_address); } } diff --git a/solidity/contracts/l2/Wormhole.sol b/solidity/contracts/l2/Wormhole.sol new file mode 100644 index 000000000..975643cc6 --- /dev/null +++ b/solidity/contracts/l2/Wormhole.sol @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-3.0-only + +// ██████████████ ▐████▌ ██████████████ +// ██████████████ ▐████▌ ██████████████ +// ▐████▌ ▐████▌ +// ▐████▌ ▐████▌ +// ██████████████ ▐████▌ ██████████████ +// ██████████████ ▐████▌ ██████████████ +// ▐████▌ ▐████▌ +// ▐████▌ ▐████▌ +// ▐████▌ ▐████▌ +// ▐████▌ ▐████▌ +// ▐████▌ ▐████▌ +// ▐████▌ ▐████▌ + +pragma solidity ^0.8.17; + +/// @title WormholeTypes +/// @notice Namespace which groups all types relevant to Wormhole interfaces. +library WormholeTypes { + /// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormholeRelayer.sol#L22 + struct VaaKey { + uint16 chainId; + bytes32 emitterAddress; + uint64 sequence; + } +} + +/// @title IWormhole +/// @notice Wormhole interface. +/// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormhole.sol#L6 +interface IWormhole { + /// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormhole.sol#L109 + function chainId() external view returns (uint16); + + /// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormhole.sol#L117 + function messageFee() external view returns (uint256); +} + +/// @title IWormholeRelayer +/// @notice Wormhole Relayer interface. +/// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormholeRelayer.sol#L74 +interface IWormholeRelayer { + /// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormholeRelayer.sol#L122 + function sendPayloadToEvm( + uint16 targetChain, + address targetAddress, + bytes memory payload, + uint256 receiverValue, + uint256 gasLimit, + uint16 refundChain, + address refundAddress + ) external payable returns (uint64 sequence); + + /// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormholeRelayer.sol#L442 + function quoteEVMDeliveryPrice( + uint16 targetChain, + uint256 receiverValue, + uint256 gasLimit + ) + external + view + returns ( + uint256 nativePriceQuote, + uint256 targetChainRefundPerGasUnused + ); + + /// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormholeRelayer.sol#L182 + function sendVaasToEvm( + uint16 targetChain, + address targetAddress, + bytes memory payload, + uint256 receiverValue, + uint256 gasLimit, + WormholeTypes.VaaKey[] memory vaaKeys, + uint16 refundChain, + address refundAddress + ) external payable returns (uint64 sequence); +} + +/// @title IWormholeReceiver +/// @notice Wormhole Receiver interface. +/// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormholeReceiver.sol#L8 +interface IWormholeReceiver { + /// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/IWormholeReceiver.sol#L44 + function receiveWormholeMessages( + bytes memory payload, + bytes[] memory additionalVaas, + bytes32 sourceAddress, + uint16 sourceChain, + bytes32 deliveryHash + ) external payable; +} + +/// @title IWormholeTokenBridge +/// @notice Wormhole Token Bridge interface. +/// @dev See: https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/2b7db51f99b49eda99b44f4a044e751cb0b2e8ea/src/interfaces/ITokenBridge.sol#L9 +interface IWormholeTokenBridge { + function completeTransferWithPayload(bytes memory encodedVm) + external + returns (bytes memory); + + function parseTransferWithPayload(bytes memory encoded) + external + pure + returns (TransferWithPayload memory transfer); + + function transferTokens( + address token, + uint256 amount, + uint16 recipientChain, + bytes32 recipient, + uint256 arbiterFee, + uint32 nonce + ) external payable returns (uint64 sequence); + + function transferTokensWithPayload( + address token, + uint256 amount, + uint16 recipientChain, + bytes32 recipient, + uint32 nonce, + bytes memory payload + ) external payable returns (uint64 sequence); + + struct TransferWithPayload { + uint8 payloadID; + uint256 amount; + bytes32 tokenAddress; + uint16 tokenChain; + bytes32 to; + uint16 toChain; + bytes32 fromAddress; + bytes payload; + } +} + +/// @title WormholeUtils +/// @notice Library for Wormhole utilities. +library WormholeUtils { + /// @notice Converts Ethereum address into Wormhole format. + /// @param _address The address to convert. + function toWormholeAddress(address _address) + internal + pure + returns (bytes32) + { + return bytes32(uint256(uint160(_address))); + } + + /// @notice Converts Wormhole address into Ethereum format. + /// @param _address The address to convert. + function fromWormholeAddress(bytes32 _address) + internal + pure + returns (address) + { + return address(uint160(uint256(_address))); + } + + /// @dev Eliminates the dust that cannot be bridged with Wormhole + /// due to the decimal shift in the Wormhole Bridge contract. + /// See https://github.com/wormhole-foundation/wormhole/blob/96682bdbeb7c87bfa110eade0554b3d8cbf788d2/ethereum/contracts/bridge/Bridge.sol#L276-L288 + function normalize(uint256 amount) internal pure returns (uint256) { + // slither-disable-next-line divide-before-multiply + amount /= 10**10; + amount *= 10**10; + return amount; + } +} diff --git a/solidity/contracts/test/WormholeBridgeStub.sol b/solidity/contracts/test/WormholeBridgeStub.sol index 91bd9d417..7afe9413c 100644 --- a/solidity/contracts/test/WormholeBridgeStub.sol +++ b/solidity/contracts/test/WormholeBridgeStub.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.17; import "./TestERC20.sol"; -import "../l2/L2WormholeGateway.sol"; +import "../l2/Wormhole.sol"; /// @dev Stub contract used in L2WormholeGateway unit tests. /// Stub contract is used instead of a smock because of the token transfer