// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error AddressInsufficientBalance(address account);
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedInnerCall();
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {FailedInnerCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert AddressInsufficientBalance(address(this));
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
* unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {FailedInnerCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert FailedInnerCall();
}
}
}
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.13;
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./interfaces/IWormholeReceiver.sol";
import "./interfaces/IWormholeRelayer.sol";
import "./interfaces/ITokenBridge.sol";
import "./interfaces/CCTPInterfaces/ITokenMessenger.sol";
import "./interfaces/CCTPInterfaces/IMessageTransmitter.sol";
import "./Utils.sol";
import {TokenBase} from "./WormholeRelayerSDK.sol";
library CCTPMessageLib {
uint8 constant CCTP_KEY_TYPE = 2;
// encoded using abi.encodePacked(domain, nonce)
struct CCTPKey {
uint32 domain;
uint64 nonce;
}
// encoded using abi.encode(message, signature)
struct CCTPMessage {
bytes message;
bytes signature;
}
}
abstract contract CCTPBase is TokenBase {
ITokenMessenger public circleTokenMessenger;
IMessageTransmitter public circleMessageTransmitter;
address public USDC;
function __CCTPBase_init(
address _wormholeRelayer,
address _tokenBridge,
address _wormhole,
address _circleMessageTransmitter,
address _circleTokenMessenger,
address _USDC
) public {
require(!_wormholeRelayerInitialized, "WRI");
TokenBase.__TokenBase_init(_wormholeRelayer, _tokenBridge, _wormhole);
circleTokenMessenger = ITokenMessenger(_circleTokenMessenger);
circleMessageTransmitter = IMessageTransmitter(_circleMessageTransmitter);
USDC = _USDC;
}
function getCCTPDomain(uint16 chain) internal pure returns (uint32) {
if (chain == 2) {
return 0;
} else if (chain == 6) {
return 1;
} else if (chain == 23) {
return 3;
} else if (chain == 24) {
return 2;
} else {
revert("Wrong CCTP Domain");
}
}
function redeemUSDC(bytes memory cctpMessage) internal returns (uint256 amount) {
(bytes memory message, bytes memory signature) = abi.decode(cctpMessage, (bytes, bytes));
uint256 beforeBalance = IERC20(USDC).balanceOf(address(this));
circleMessageTransmitter.receiveMessage(message, signature);
return IERC20(USDC).balanceOf(address(this)) - beforeBalance;
}
}
abstract contract CCTPSender is CCTPBase {
uint8 internal constant CONSISTENCY_LEVEL_FINALIZED = 15;
using CCTPMessageLib for *;
/**
* transferTokens wraps common boilerplate for sending tokens to another chain using IWormholeRelayer
* - approves tokenBridge to spend 'amount' of 'token'
* - emits token transfer VAA
* - returns VAA key for inclusion in WormholeRelayer `additionalVaas` argument
*
* Note: this requires that only the targetAddress can redeem transfers.
*
*/
function transferUSDC(uint256 amount, uint16 targetChain, address targetAddress)
internal
returns (MessageKey memory)
{
SafeERC20.forceApprove(IERC20(USDC), address(circleTokenMessenger), amount);
uint64 nonce = circleTokenMessenger.depositForBurnWithCaller(
amount,
getCCTPDomain(targetChain),
addressToBytes32CCTP(targetAddress),
USDC,
addressToBytes32CCTP(targetAddress)
);
return MessageKey(
CCTPMessageLib.CCTP_KEY_TYPE, abi.encodePacked(getCCTPDomain(wormhole.chainId()), nonce)
);
}
function sendUSDCWithPayloadToEvm(
uint16 targetChain,
address targetAddress,
bytes memory payload,
uint256 receiverValue,
uint256 gasLimit,
uint256 amount,
uint16 refundChain,
address refundAddress
) internal returns (uint64 sequence) {
MessageKey[] memory messageKeys = new MessageKey[](1);
messageKeys[0] = transferUSDC(amount, targetChain, targetAddress);
address defaultDeliveryProvider = wormholeRelayer.getDefaultDeliveryProvider();
(uint256 cost,) = wormholeRelayer.quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit);
sequence = wormholeRelayer.sendToEvm{value: cost}(
targetChain,
targetAddress,
abi.encode(amount, payload),
receiverValue,
0,
gasLimit,
refundChain,
refundAddress,
defaultDeliveryProvider,
messageKeys,
CONSISTENCY_LEVEL_FINALIZED
);
}
function addressToBytes32CCTP(address addr) private pure returns (bytes32) {
return bytes32(uint256(uint160(addr)));
}
}
abstract contract CCTPReceiver is CCTPBase {
function receiveWormholeMessages(
bytes memory payload,
bytes[] memory additionalMessages,
bytes32 sourceAddress,
uint16 sourceChain,
bytes32 deliveryHash
) external virtual payable {
_receiveWormholeMessagesWithCCTP(payload, additionalMessages, sourceAddress, sourceChain, deliveryHash);
}
function _receiveWormholeMessagesWithCCTP(
bytes memory payload,
bytes[] memory additionalMessages,
bytes32 sourceAddress,
uint16 sourceChain,
bytes32 deliveryHash
) internal {
require(additionalMessages.length <= 1, "CCTP: At most one Message is supported");
uint256 amountUSDCReceived;
if (additionalMessages.length == 1) {
amountUSDCReceived = redeemUSDC(additionalMessages[0]);
}
(uint256 amount, bytes memory userPayload) = abi.decode(payload, (uint256, bytes));
// Check that the correct amount was received
// It is important to verify that the 'USDC' received is
require(amount == amountUSDCReceived, "Wrong amount received");
receivePayloadAndUSDC(userPayload, amountUSDCReceived, sourceAddress, sourceChain, deliveryHash);
}
function receivePayloadAndUSDC(
bytes memory payload,
uint256 amountUSDCReceived,
bytes32 sourceAddress,
uint16 sourceChain,
bytes32 deliveryHash
) internal virtual {}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
/**
* @title HubSpokeStructs
* @notice A set of structs and enums used in the Hub and Spoke contracts
*/
contract HubSpokeStructs {
/**
* @param wormhole: Address of the Wormhole contract
* @param tokenBridge: Address of the TokenBridge contract
* @param wormholeRelayer: Address of the WormholeRelayer contract
* @param consistencyLevel: Desired level of finality the Wormhole guardians will reach before signing the messages
* NOTE: consistencyLevel = 200 will result in an instant message, while all other values will wait for finality
* Recommended finality levels can be found here: https://book.wormhole.com/reference/contracts.html
* @param pythAddress: Address of the Pyth oracle on the Hub chain
* @param priceStandardDeviations: priceStandardDeviations = (psd * priceStandardDeviationsPrecision), where psd is
* the number of standard deviations that we use for our price intervals in calculations relating to allowing
* withdraws, borrows, or liquidations
* @param priceStandardDeviationsPrecision: A precision number that allows us to represent our desired noninteger
* price standard deviation as an integer (psd = priceStandardDeviations/priceStandardDeviationsPrecision)
* @param maxLiquidationPortionPrecision: A precision number that allows us to represent our desired noninteger
* max liquidation portion mlp as an integer (mlp = maxLiquidationPortion/maxLiquidationPortionPrecision)
* @param interestAccrualIndexPrecision: A precision number that allows us to represent our noninteger interest
* accrual indices as integers; we store each index as its true value multiplied by interestAccrualIndexPrecision
* @param collateralizationRatioPrecision: A precision number that allows us to represent our noninteger
* collateralization ratios as integers; we store each ratio as its true value multiplied by
* collateralizationRatioPrecision
* @param liquidationFee: The fee taken by the protocol on liquidation
* @param _circleMessageTransmitter: Cicle Message Transmitter contract (cctp)
* @param _circleTokenMessenger: Cicle Token Messenger contract (cctp)
* @param _USDC: USDC token contract (cctp)
*/
struct ConstructorArgs {
/* Wormhole Information */
address wormhole;
address tokenBridge;
address wormholeRelayer;
uint8 consistencyLevel;
/* Liquidation Information */
uint256 interestAccrualIndexPrecision;
uint256 liquidationFee;
uint256 liquidationFeePrecision;
/* CCTP Information */
address circleMessageTransmitter;
address circleTokenMessenger;
address USDC;
}
struct StoredVaultAmount {
DenormalizedVaultAmount amounts;
AccrualIndices accrualIndices;
}
struct DenormalizedVaultAmount {
uint256 deposited;
uint256 borrowed;
}
struct NotionalVaultAmount {
uint256 deposited;
uint256 borrowed;
}
struct AccrualIndices {
uint256 deposited;
uint256 borrowed;
}
struct AssetInfo {
uint256 collateralizationRatioDeposit;
uint256 collateralizationRatioBorrow;
uint8 decimals;
address interestRateCalculator;
bool exists;
uint256 borrowLimit;
uint256 supplyLimit;
uint256 maxLiquidationPortion;
uint256 maxLiquidationBonus; // 1e6 precision; 130e4 = 130% = 1.3; the liquidator gets 30% over what he repays
}
/**
* @dev Struct to hold the decoded data from a Wormhole payload
* @param action The action to be performed (e.g., Deposit, Borrow, Withdraw, Repay)
* @param sender The address of the sender initiating the action
* @param wrappedAsset The address of the wrapped asset involved in the action
* @param amount The amount of the wrapped asset involved in the action
* @param unwrap A boolean indicating whether to unwrap the asset or not for native withdraws and borrows
*/
struct PayloadData {
Action action;
address sender;
address wrappedAsset;
uint256 amount;
bool unwrap;
}
enum Action {
Deposit,
Borrow,
Withdraw,
Repay,
DepositNative,
RepayNative
}
enum Round {
UP,
DOWN
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IERC20decimals is IERC20 {
function decimals() external view returns (uint8);
}
/*
* Copyright (c) 2022, Circle Internet Financial Limited.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
pragma solidity ^0.8.0;
import "./IRelayer.sol";
import "./IReceiver.sol";
/**
* @title IMessageTransmitter
* @notice Interface for message transmitters, which both relay and receive messages.
*/
interface IMessageTransmitter is IRelayer, IReceiver {
}
/*
* Copyright (c) 2022, Circle Internet Financial Limited.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
pragma solidity ^0.8.0;
/**
* @title IReceiver
* @notice Receives messages on destination chain and forwards them to IMessageDestinationHandler
*/
interface IReceiver {
/**
* @notice Receives an incoming message, validating the header and passing
* the body to application-specific handler.
* @param message The message raw bytes
* @param signature The message signature
* @return success bool, true if successful
*/
function receiveMessage(bytes calldata message, bytes calldata signature)
external
returns (bool success);
}
/*
* Copyright (c) 2022, Circle Internet Financial Limited.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
pragma solidity ^0.8.0;
/**
* @title IRelayer
* @notice Sends messages from source domain to destination domain
*/
interface IRelayer {
/**
* @notice Sends an outgoing message from the source domain.
* @dev Increment nonce, format the message, and emit `MessageSent` event with message information.
* @param destinationDomain Domain of destination chain
* @param recipient Address of message recipient on destination domain as bytes32
* @param messageBody Raw bytes content of message
* @return nonce reserved by message
*/
function sendMessage(
uint32 destinationDomain,
bytes32 recipient,
bytes calldata messageBody
) external returns (uint64);
/**
* @notice Sends an outgoing message from the source domain, with a specified caller on the
* destination domain.
* @dev Increment nonce, format the message, and emit `MessageSent` event with message information.
* WARNING: if the `destinationCaller` does not represent a valid address as bytes32, then it will not be possible
* to broadcast the message on the destination domain. This is an advanced feature, and the standard
* sendMessage() should be preferred for use cases where a specific destination caller is not required.
* @param destinationDomain Domain of destination chain
* @param recipient Address of message recipient on destination domain as bytes32
* @param destinationCaller caller on the destination domain, as bytes32
* @param messageBody Raw bytes content of message
* @return nonce reserved by message
*/
function sendMessageWithCaller(
uint32 destinationDomain,
bytes32 recipient,
bytes32 destinationCaller,
bytes calldata messageBody
) external returns (uint64);
/**
* @notice Replace a message with a new message body and/or destination caller.
* @dev The `originalAttestation` must be a valid attestation of `originalMessage`.
* @param originalMessage original message to replace
* @param originalAttestation attestation of `originalMessage`
* @param newMessageBody new message body of replaced message
* @param newDestinationCaller the new destination caller
*/
function replaceMessage(
bytes calldata originalMessage,
bytes calldata originalAttestation,
bytes calldata newMessageBody,
bytes32 newDestinationCaller
) external;
}
// contracts/Bridge.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
import "./IWETH.sol";
import "./IWormhole.sol";
interface ITokenBridge {
struct Transfer {
uint8 payloadID;
uint256 amount;
bytes32 tokenAddress;
uint16 tokenChain;
bytes32 to;
uint16 toChain;
uint256 fee;
}
struct TransferWithPayload {
uint8 payloadID;
uint256 amount;
bytes32 tokenAddress;
uint16 tokenChain;
bytes32 to;
uint16 toChain;
bytes32 fromAddress;
bytes payload;
}
struct AssetMeta {
uint8 payloadID;
bytes32 tokenAddress;
uint16 tokenChain;
uint8 decimals;
bytes32 symbol;
bytes32 name;
}
struct RegisterChain {
bytes32 module;
uint8 action;
uint16 chainId;
uint16 emitterChainID;
bytes32 emitterAddress;
}
struct UpgradeContract {
bytes32 module;
uint8 action;
uint16 chainId;
bytes32 newContract;
}
struct RecoverChainId {
bytes32 module;
uint8 action;
uint256 evmChainId;
uint16 newChainId;
}
event ContractUpgraded(address indexed oldContract, address indexed newContract);
function _parseTransferCommon(bytes memory encoded) external pure returns (Transfer memory transfer);
function attestToken(address tokenAddress, uint32 nonce) external payable returns (uint64 sequence);
function wrapAndTransferETH(uint16 recipientChain, bytes32 recipient, uint256 arbiterFee, uint32 nonce)
external
payable
returns (uint64 sequence);
function wrapAndTransferETHWithPayload(uint16 recipientChain, bytes32 recipient, uint32 nonce, bytes memory payload)
external
payable
returns (uint64 sequence);
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);
function updateWrapped(bytes memory encodedVm) external returns (address token);
function createWrapped(bytes memory encodedVm) external returns (address token);
function completeTransferWithPayload(bytes memory encodedVm) external returns (bytes memory);
function completeTransferAndUnwrapETHWithPayload(bytes memory encodedVm) external returns (bytes memory);
function completeTransfer(bytes memory encodedVm) external;
function completeTransferAndUnwrapETH(bytes memory encodedVm) external;
function encodeAssetMeta(AssetMeta memory meta) external pure returns (bytes memory encoded);
function encodeTransfer(Transfer memory transfer) external pure returns (bytes memory encoded);
function encodeTransferWithPayload(TransferWithPayload memory transfer)
external
pure
returns (bytes memory encoded);
function parsePayloadID(bytes memory encoded) external pure returns (uint8 payloadID);
function parseAssetMeta(bytes memory encoded) external pure returns (AssetMeta memory meta);
function parseTransfer(bytes memory encoded) external pure returns (Transfer memory transfer);
function parseTransferWithPayload(bytes memory encoded)
external
pure
returns (TransferWithPayload memory transfer);
function governanceActionIsConsumed(bytes32 hash) external view returns (bool);
function isInitialized(address impl) external view returns (bool);
function isTransferCompleted(bytes32 hash) external view returns (bool);
function wormhole() external view returns (IWormhole);
function chainId() external view returns (uint16);
function evmChainId() external view returns (uint256);
function isFork() external view returns (bool);
function governanceChainId() external view returns (uint16);
function governanceContract() external view returns (bytes32);
function wrappedAsset(uint16 tokenChainId, bytes32 tokenAddress) external view returns (address);
function bridgeContracts(uint16 chainId_) external view returns (bytes32);
function tokenImplementation() external view returns (address);
function WETH() external view returns (IWETH);
function outstandingBridged(address token) external view returns (uint256);
function isWrappedAsset(address token) external view returns (bool);
function finality() external view returns (uint8);
function implementation() external view returns (address);
function initialize() external;
function registerChain(bytes memory encodedVM) external;
function upgrade(bytes memory encodedVM) external;
function submitRecoverChainId(bytes memory encodedVM) external;
function parseRegisterChain(bytes memory encoded) external pure returns (RegisterChain memory chain);
function parseUpgrade(bytes memory encoded) external pure returns (UpgradeContract memory chain);
function parseRecoverChainId(bytes memory encodedRecoverChainId)
external
pure
returns (RecoverChainId memory rci);
}
pragma solidity ^0.8.0;
interface ITokenMessenger {
/**
* @notice Deposits and burns tokens from sender to be minted on destination domain. The mint
* on the destination domain must be called by `destinationCaller`.
* WARNING: if the `destinationCaller` does not represent a valid address as bytes32, then it will not be possible
* to broadcast the message on the destination domain. This is an advanced feature, and the standard
* depositForBurn() should be preferred for use cases where a specific destination caller is not required.
* Emits a `DepositForBurn` event.
* @dev reverts if:
* - given destinationCaller is zero address
* - given burnToken is not supported
* - given destinationDomain has no TokenMessenger registered
* - transferFrom() reverts. For example, if sender's burnToken balance or approved allowance
* to this contract is less than `amount`.
* - burn() reverts. For example, if `amount` is 0.
* - MessageTransmitter returns false or reverts.
* @param amount amount of tokens to burn
* @param destinationDomain destination domain
* @param mintRecipient address of mint recipient on destination domain
* @param burnToken address of contract to burn deposited tokens, on local domain
* @param destinationCaller caller on the destination domain, as bytes32
* @return nonce unique nonce reserved by message
*/
function depositForBurnWithCaller(
uint256 amount,
uint32 destinationDomain,
bytes32 mintRecipient,
address burnToken,
bytes32 destinationCaller
) external returns (uint64 nonce);
}
// contracts/Bridge.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IWETH is IERC20 {
function deposit() external payable;
function withdraw(uint256 amount) external;
}
// contracts/Messages.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
interface IWormhole {
struct GuardianSet {
address[] keys;
uint32 expirationTime;
}
struct Signature {
bytes32 r;
bytes32 s;
uint8 v;
uint8 guardianIndex;
}
struct VM {
uint8 version;
uint32 timestamp;
uint32 nonce;
uint16 emitterChainId;
bytes32 emitterAddress;
uint64 sequence;
uint8 consistencyLevel;
bytes payload;
uint32 guardianSetIndex;
Signature[] signatures;
bytes32 hash;
}
struct ContractUpgrade {
bytes32 module;
uint8 action;
uint16 chain;
address newContract;
}
struct GuardianSetUpgrade {
bytes32 module;
uint8 action;
uint16 chain;
GuardianSet newGuardianSet;
uint32 newGuardianSetIndex;
}
struct SetMessageFee {
bytes32 module;
uint8 action;
uint16 chain;
uint256 messageFee;
}
struct TransferFees {
bytes32 module;
uint8 action;
uint16 chain;
uint256 amount;
bytes32 recipient;
}
struct RecoverChainId {
bytes32 module;
uint8 action;
uint256 evmChainId;
uint16 newChainId;
}
event LogMessagePublished(
address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel
);
event ContractUpgraded(address indexed oldContract, address indexed newContract);
event GuardianSetAdded(uint32 indexed index);
function publishMessage(uint32 nonce, bytes memory payload, uint8 consistencyLevel)
external
payable
returns (uint64 sequence);
function initialize() external;
function parseAndVerifyVM(bytes calldata encodedVM)
external
view
returns (VM memory vm, bool valid, string memory reason);
function verifyVM(VM memory vm) external view returns (bool valid, string memory reason);
function verifySignatures(bytes32 hash, Signature[] memory signatures, GuardianSet memory guardianSet)
external
pure
returns (bool valid, string memory reason);
function parseVM(bytes memory encodedVM) external pure returns (VM memory vm);
function quorum(uint256 numGuardians) external pure returns (uint256 numSignaturesRequiredForQuorum);
function getGuardianSet(uint32 index) external view returns (GuardianSet memory);
function getCurrentGuardianSetIndex() external view returns (uint32);
function getGuardianSetExpiry() external view returns (uint32);
function governanceActionIsConsumed(bytes32 hash) external view returns (bool);
function isInitialized(address impl) external view returns (bool);
function chainId() external view returns (uint16);
function isFork() external view returns (bool);
function governanceChainId() external view returns (uint16);
function governanceContract() external view returns (bytes32);
function messageFee() external view returns (uint256);
function evmChainId() external view returns (uint256);
function nextSequence(address emitter) external view returns (uint64);
function parseContractUpgrade(bytes memory encodedUpgrade) external pure returns (ContractUpgrade memory cu);
function parseGuardianSetUpgrade(bytes memory encodedUpgrade)
external
pure
returns (GuardianSetUpgrade memory gsu);
function parseSetMessageFee(bytes memory encodedSetMessageFee) external pure returns (SetMessageFee memory smf);
function parseTransferFees(bytes memory encodedTransferFees) external pure returns (TransferFees memory tf);
function parseRecoverChainId(bytes memory encodedRecoverChainId)
external
pure
returns (RecoverChainId memory rci);
function submitContractUpgrade(bytes memory _vm) external;
function submitSetMessageFee(bytes memory _vm) external;
function submitNewGuardianSet(bytes memory _vm) external;
function submitTransferFees(bytes memory _vm) external;
function submitRecoverChainId(bytes memory _vm) external;
}
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
/**
* @notice Interface for a contract which can receive Wormhole messages.
*/
interface IWormholeReceiver {
/**
* @notice When a `send` is performed with this contract as the target, this function will be
* invoked by the WormholeRelayer contract
*
* NOTE: This function should be restricted such that only the Wormhole Relayer contract can call it.
*
* We also recommend that this function:
* - Stores all received `deliveryHash`s in a mapping `(bytes32 => bool)`, and
* on every call, checks that deliveryHash has not already been stored in the
* map (This is to prevent other users maliciously trying to relay the same message)
* - Checks that `sourceChain` and `sourceAddress` are indeed who
* you expect to have requested the calling of `send` on the source chain
*
* The invocation of this function corresponding to the `send` request will have msg.value equal
* to the receiverValue specified in the send request.
*
* If the invocation of this function reverts or exceeds the gas limit
* specified by the send requester, this delivery will result in a `ReceiverFailure`.
*
* @param payload - an arbitrary message which was included in the delivery by the
* requester.
* @param additionalVaas - Additional VAAs which were requested to be included in this delivery.
* They are guaranteed to all be included and in the same order as was specified in the
* delivery request.
* @param sourceAddress - the (wormhole format) address on the sending chain which requested
* this delivery.
* @param sourceChain - the wormhole chain ID where this delivery was requested.
* @param deliveryHash - the VAA hash of the deliveryVAA.
*
* NOTE: These signedVaas are NOT verified by the Wormhole core contract prior to being provided
* to this call. Always make sure `parseAndVerify()` is called on the Wormhole core contract
* before trusting the content of a raw VAA, otherwise the VAA may be invalid or malicious.
*/
function receiveWormholeMessages(
bytes memory payload,
bytes[] memory additionalVaas,
bytes32 sourceAddress,
uint16 sourceChain,
bytes32 deliveryHash
) external payable;
}
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
/**
* @title WormholeRelayer
* @author
* @notice This project allows developers to build cross-chain applications powered by Wormhole without needing to
* write and run their own relaying infrastructure
*
* We implement the IWormholeRelayer interface that allows users to request a delivery provider to relay a payload (and/or additional VAAs)
* to a chain and address of their choice.
*/
/**
* @notice VaaKey identifies a wormhole message
*
* @custom:member chainId Wormhole chain ID of the chain where this VAA was emitted from
* @custom:member emitterAddress Address of the emitter of the VAA, in Wormhole bytes32 format
* @custom:member sequence Sequence number of the VAA
*/
struct VaaKey {
uint16 chainId;
bytes32 emitterAddress;
uint64 sequence;
}
// 0-127 are reserved for standardized KeyTypes, 128-255 are for custom use
uint8 constant VAA_KEY_TYPE = 1;
struct MessageKey {
uint8 keyType; // 0-127 are reserved for standardized KeyTypes, 128-255 are for custom use
bytes encodedKey;
}
interface IWormholeRelayerBase {
event SendEvent(
uint64 indexed sequence, uint256 deliveryQuote, uint256 paymentForExtraReceiverValue
);
function getRegisteredWormholeRelayerContract(uint16 chainId) external view returns (bytes32);
/**
* @notice Returns true if a delivery has been attempted for the given deliveryHash
* Note: invalid deliveries where the tx reverts are not considered attempted
*/
function deliveryAttempted(bytes32 deliveryHash) external view returns (bool attempted);
/**
* @notice block number at which a delivery was successfully executed
*/
function deliverySuccessBlock(bytes32 deliveryHash) external view returns (uint256 blockNumber);
/**
* @notice block number of the latest attempt to execute a delivery that failed
*/
function deliveryFailureBlock(bytes32 deliveryHash) external view returns (uint256 blockNumber);
}
/**
* @title IWormholeRelayerSend
* @notice The interface to request deliveries
*/
interface IWormholeRelayerSend is IWormholeRelayerBase {
/**
* @notice Publishes an instruction for the default delivery provider
* to relay a payload to the address `targetAddress` on chain `targetChain`
* with gas limit `gasLimit` and `msg.value` equal to `receiverValue`
*
* `targetAddress` must implement the IWormholeReceiver interface
*
* This function must be called with `msg.value` equal to `quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit)`
*
* Any refunds (from leftover gas) will be paid to the delivery provider. In order to receive the refunds, use the `sendPayloadToEvm` function
* with `refundChain` and `refundAddress` as parameters
*
* @param targetChain in Wormhole Chain ID format
* @param targetAddress address to call on targetChain (that implements IWormholeReceiver)
* @param payload arbitrary bytes to pass in as parameter in call to `targetAddress`
* @param receiverValue msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
* @param gasLimit gas limit with which to call `targetAddress`.
* @return sequence sequence number of published VAA containing delivery instructions
*/
function sendPayloadToEvm(
uint16 targetChain,
address targetAddress,
bytes memory payload,
uint256 receiverValue,
uint256 gasLimit
) external payable returns (uint64 sequence);
/**
* @notice Publishes an instruction for the default delivery provider
* to relay a payload to the address `targetAddress` on chain `targetChain`
* with gas limit `gasLimit` and `msg.value` equal to `receiverValue`
*
* Any refunds (from leftover gas) will be sent to `refundAddress` on chain `refundChain`
* `targetAddress` must implement the IWormholeReceiver interface
*
* This function must be called with `msg.value` equal to `quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit)`
*
* @param targetChain in Wormhole Chain ID format
* @param targetAddress address to call on targetChain (that implements IWormholeReceiver)
* @param payload arbitrary bytes to pass in as parameter in call to `targetAddress`
* @param receiverValue msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
* @param gasLimit gas limit with which to call `targetAddress`. Any units of gas unused will be refunded according to the
* `targetChainRefundPerGasUnused` rate quoted by the delivery provider
* @param refundChain The chain to deliver any refund to, in Wormhole Chain ID format
* @param refundAddress The address on `refundChain` to deliver any refund to
* @return sequence sequence number of published VAA containing delivery instructions
*/
function sendPayloadToEvm(
uint16 targetChain,
address targetAddress,
bytes memory payload,
uint256 receiverValue,
uint256 gasLimit,
uint16 refundChain,
address refundAddress
) external payable returns (uint64 sequence);
/**
* @notice Publishes an instruction for the default delivery provider
* to relay a payload and VAAs specified by `vaaKeys` to the address `targetAddress` on chain `targetChain`
* with gas limit `gasLimit` and `msg.value` equal to `receiverValue`
*
* `targetAddress` must implement the IWormholeReceiver interface
*
* This function must be called with `msg.value` equal to `quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit)`
*
* Any refunds (from leftover gas) will be paid to the delivery provider. In order to receive the refunds, use the `sendVaasToEvm` function
* with `refundChain` and `refundAddress` as parameters
*
* @param targetChain in Wormhole Chain ID format
* @param targetAddress address to call on targetChain (that implements IWormholeReceiver)
* @param payload arbitrary bytes to pass in as parameter in call to `targetAddress`
* @param receiverValue msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
* @param gasLimit gas limit with which to call `targetAddress`.
* @param vaaKeys Additional VAAs to pass in as parameter in call to `targetAddress`
* @return sequence sequence number of published VAA containing delivery instructions
*/
function sendVaasToEvm(
uint16 targetChain,
address targetAddress,
bytes memory payload,
uint256 receiverValue,
uint256 gasLimit,
VaaKey[] memory vaaKeys
) external payable returns (uint64 sequence);
/**
* @notice Publishes an instruction for the default delivery provider
* to relay a payload and VAAs specified by `vaaKeys` to the address `targetAddress` on chain `targetChain`
* with gas limit `gasLimit` and `msg.value` equal to `receiverValue`
*
* Any refunds (from leftover gas) will be sent to `refundAddress` on chain `refundChain`
* `targetAddress` must implement the IWormholeReceiver interface
*
* This function must be called with `msg.value` equal to `quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit)`
*
* @param targetChain in Wormhole Chain ID format
* @param targetAddress address to call on targetChain (that implements IWormholeReceiver)
* @param payload arbitrary bytes to pass in as parameter in call to `targetAddress`
* @param receiverValue msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
* @param gasLimit gas limit with which to call `targetAddress`. Any units of gas unused will be refunded according to the
* `targetChainRefundPerGasUnused` rate quoted by the delivery provider
* @param vaaKeys Additional VAAs to pass in as parameter in call to `targetAddress`
* @param refundChain The chain to deliver any refund to, in Wormhole Chain ID format
* @param refundAddress The address on `refundChain` to deliver any refund to
* @return sequence sequence number of published VAA containing delivery instructions
*/
function sendVaasToEvm(
uint16 targetChain,
address targetAddress,
bytes memory payload,
uint256 receiverValue,
uint256 gasLimit,
VaaKey[] memory vaaKeys,
uint16 refundChain,
address refundAddress
) external payable returns (uint64 sequence);
/**
* @notice Publishes an instruction for the delivery provider at `deliveryProviderAddress`
* to relay a payload and VAAs specified by `vaaKeys` to the address `targetAddress` on chain `targetChain`
* with gas limit `gasLimit` and `msg.value` equal to
* receiverValue + (arbitrary amount that is paid for by paymentForExtraReceiverValue of this chain's wei) in targetChain wei.
*
* Any refunds (from leftover gas) will be sent to `refundAddress` on chain `refundChain`
* `targetAddress` must implement the IWormholeReceiver interface
*
* This function must be called with `msg.value` equal to
* quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit, deliveryProviderAddress) + paymentForExtraReceiverValue
*
* @param targetChain in Wormhole Chain ID format
* @param targetAddress address to call on targetChain (that implements IWormholeReceiver)
* @param payload arbitrary bytes to pass in as parameter in call to `targetAddress`
* @param receiverValue msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
* @param paymentForExtraReceiverValue amount (in current chain currency units) to spend on extra receiverValue
* (in addition to the `receiverValue` specified)
* @param gasLimit gas limit with which to call `targetAddress`. Any units of gas unused will be refunded according to the
* `targetChainRefundPerGasUnused` rate quoted by the delivery provider
* @param refundChain The chain to deliver any refund to, in Wormhole Chain ID format
* @param refundAddress The address on `refundChain` to deliver any refund to
* @param deliveryProviderAddress The address of the desired delivery provider's implementation of IDeliveryProvider
* @param vaaKeys Additional VAAs to pass in as parameter in call to `targetAddress`
* @param consistencyLevel Consistency level with which to publish the delivery instructions - see
* https://book.wormhole.com/wormhole/3_coreLayerContracts.html?highlight=consistency#consistency-levels
* @return sequence sequence number of published VAA containing delivery instructions
*/
function sendToEvm(
uint16 targetChain,
address targetAddress,
bytes memory payload,
uint256 receiverValue,
uint256 paymentForExtraReceiverValue,
uint256 gasLimit,
uint16 refundChain,
address refundAddress,
address deliveryProviderAddress,
VaaKey[] memory vaaKeys,
uint8 consistencyLevel
) external payable returns (uint64 sequence);
/**
* @notice Publishes an instruction for the delivery provider at `deliveryProviderAddress`
* to relay a payload and external messages specified by `messageKeys` to the address `targetAddress` on chain `targetChain`
* with gas limit `gasLimit` and `msg.value` equal to
* receiverValue + (arbitrary amount that is paid for by paymentForExtraReceiverValue of this chain's wei) in targetChain wei.
*
* Any refunds (from leftover gas) will be sent to `refundAddress` on chain `refundChain`
* `targetAddress` must implement the IWormholeReceiver interface
*
* This function must be called with `msg.value` equal to
* quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit, deliveryProviderAddress) + paymentForExtraReceiverValue
*
* MessageKeys can specify wormhole messages (VaaKeys) or other types of messages (ex. USDC CCTP attestations). Ensure the selected
* Note: DeliveryProvider supports all the MessageKey.keyType values specified or it will not be delivered!
*
* @param targetChain in Wormhole Chain ID format
* @param targetAddress address to call on targetChain (that implements IWormholeReceiver)
* @param payload arbitrary bytes to pass in as parameter in call to `targetAddress`
* @param receiverValue msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
* @param paymentForExtraReceiverValue amount (in current chain currency units) to spend on extra receiverValue
* (in addition to the `receiverValue` specified)
* @param gasLimit gas limit with which to call `targetAddress`. Any units of gas unused will be refunded according to the
* `targetChainRefundPerGasUnused` rate quoted by the delivery provider
* @param refundChain The chain to deliver any refund to, in Wormhole Chain ID format
* @param refundAddress The address on `refundChain` to deliver any refund to
* @param deliveryProviderAddress The address of the desired delivery provider's implementation of IDeliveryProvider
* @param messageKeys Additional messagess to pass in as parameter in call to `targetAddress`
* @param consistencyLevel Consistency level with which to publish the delivery instructions - see
* https://book.wormhole.com/wormhole/3_coreLayerContracts.html?highlight=consistency#consistency-levels
* @return sequence sequence number of published VAA containing delivery instructions
*/
function sendToEvm(
uint16 targetChain,
address targetAddress,
bytes memory payload,
uint256 receiverValue,
uint256 paymentForExtraReceiverValue,
uint256 gasLimit,
uint16 refundChain,
address refundAddress,
address deliveryProviderAddress,
MessageKey[] memory messageKeys,
uint8 consistencyLevel
) external payable returns (uint64 sequence);
/**
* @notice Publishes an instruction for the delivery provider at `deliveryProviderAddress`
* to relay a payload and VAAs specified by `vaaKeys` to the address `targetAddress` on chain `targetChain`
* with `msg.value` equal to
* receiverValue + (arbitrary amount that is paid for by paymentForExtraReceiverValue of this chain's wei) in targetChain wei.
*
* Any refunds (from leftover gas) will be sent to `refundAddress` on chain `refundChain`
* `targetAddress` must implement the IWormholeReceiver interface
*
* This function must be called with `msg.value` equal to
* quoteDeliveryPrice(targetChain, receiverValue, encodedExecutionParameters, deliveryProviderAddress) + paymentForExtraReceiverValue
*
* @param targetChain in Wormhole Chain ID format
* @param targetAddress address to call on targetChain (that implements IWormholeReceiver), in Wormhole bytes32 format
* @param payload arbitrary bytes to pass in as parameter in call to `targetAddress`
* @param receiverValue msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
* @param paymentForExtraReceiverValue amount (in current chain currency units) to spend on extra receiverValue
* (in addition to the `receiverValue` specified)
* @param encodedExecutionParameters encoded information on how to execute delivery that may impact pricing
* e.g. for version EVM_V1, this is a struct that encodes the `gasLimit` with which to call `targetAddress`
* @param refundChain The chain to deliver any refund to, in Wormhole Chain ID format
* @param refundAddress The address on `refundChain` to deliver any refund to, in Wormhole bytes32 format
* @param deliveryProviderAddress The address of the desired delivery provider's implementation of IDeliveryProvider
* @param vaaKeys Additional VAAs to pass in as parameter in call to `targetAddress`
* @param consistencyLevel Consistency level with which to publish the delivery instructions - see
* https://book.wormhole.com/wormhole/3_coreLayerContracts.html?highlight=consistency#consistency-levels
* @return sequence sequence number of published VAA containing delivery instructions
*/
function send(
uint16 targetChain,
bytes32 targetAddress,
bytes memory payload,
uint256 receiverValue,
uint256 paymentForExtraReceiverValue,
bytes memory encodedExecutionParameters,
uint16 refundChain,
bytes32 refundAddress,
address deliveryProviderAddress,
VaaKey[] memory vaaKeys,
uint8 consistencyLevel
) external payable returns (uint64 sequence);
/**
* @notice Publishes an instruction for the delivery provider at `deliveryProviderAddress`
* to relay a payload and VAAs specified by `vaaKeys` to the address `targetAddress` on chain `targetChain`
* with `msg.value` equal to
* receiverValue + (arbitrary amount that is paid for by paymentForExtraReceiverValue of this chain's wei) in targetChain wei.
*
* Any refunds (from leftover gas) will be sent to `refundAddress` on chain `refundChain`
* `targetAddress` must implement the IWormholeReceiver interface
*
* This function must be called with `msg.value` equal to
* quoteDeliveryPrice(targetChain, receiverValue, encodedExecutionParameters, deliveryProviderAddress) + paymentForExtraReceiverValue
*
* MessageKeys can specify wormhole messages (VaaKeys) or other types of messages (ex. USDC CCTP attestations). Ensure the selected
* Note: DeliveryProvider supports all the MessageKey.keyType values specified or it will not be delivered!
*
* @param targetChain in Wormhole Chain ID format
* @param targetAddress address to call on targetChain (that implements IWormholeReceiver), in Wormhole bytes32 format
* @param payload arbitrary bytes to pass in as parameter in call to `targetAddress`
* @param receiverValue msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
* @param paymentForExtraReceiverValue amount (in current chain currency units) to spend on extra receiverValue
* (in addition to the `receiverValue` specified)
* @param encodedExecutionParameters encoded information on how to execute delivery that may impact pricing
* e.g. for version EVM_V1, this is a struct that encodes the `gasLimit` with which to call `targetAddress`
* @param refundChain The chain to deliver any refund to, in Wormhole Chain ID format
* @param refundAddress The address on `refundChain` to deliver any refund to, in Wormhole bytes32 format
* @param deliveryProviderAddress The address of the desired delivery provider's implementation of IDeliveryProvider
* @param messageKeys Additional messagess to pass in as parameter in call to `targetAddress`
* @param consistencyLevel Consistency level with which to publish the delivery instructions - see
* https://book.wormhole.com/wormhole/3_coreLayerContracts.html?highlight=consistency#consistency-levels
* @return sequence sequence number of published VAA containing delivery instructions
*/
function send(
uint16 targetChain,
bytes32 targetAddress,
bytes memory payload,
uint256 receiverValue,
uint256 paymentForExtraReceiverValue,
bytes memory encodedExecutionParameters,
uint16 refundChain,
bytes32 refundAddress,
address deliveryProviderAddress,
MessageKey[] memory messageKeys,
uint8 consistencyLevel
) external payable returns (uint64 sequence);
/**
* @notice Requests a previously published delivery instruction to be redelivered
* (e.g. with a different delivery provider)
*
* This function must be called with `msg.value` equal to
* quoteEVMDeliveryPrice(targetChain, newReceiverValue, newGasLimit, newDeliveryProviderAddress)
*
* @notice *** This will only be able to succeed if the following is true **
* - newGasLimit >= gas limit of the old instruction
* - newReceiverValue >= receiver value of the old instruction
* - newDeliveryProvider's `targetChainRefundPerGasUnused` >= old relay provider's `targetChainRefundPerGasUnused`
*
* @param deliveryVaaKey VaaKey identifying the wormhole message containing the
* previously published delivery instructions
* @param targetChain The target chain that the original delivery targeted. Must match targetChain from original delivery instructions
* @param newReceiverValue new msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
* @param newGasLimit gas limit with which to call `targetAddress`. Any units of gas unused will be refunded according to the
* `targetChainRefundPerGasUnused` rate quoted by the delivery provider, to the refund chain and address specified in the original request
* @param newDeliveryProviderAddress The address of the desired delivery provider's implementation of IDeliveryProvider
* @return sequence sequence number of published VAA containing redelivery instructions
*
* @notice *** This will only be able to succeed if the following is true **
* - newGasLimit >= gas limit of the old instruction
* - newReceiverValue >= receiver value of the old instruction
* - newDeliveryProvider's `targetChainRefundPerGasUnused` >= old relay provider's `targetChainRefundPerGasUnused`
*/
function resendToEvm(
VaaKey memory deliveryVaaKey,
uint16 targetChain,
uint256 newReceiverValue,
uint256 newGasLimit,
address newDeliveryProviderAddress
) external payable returns (uint64 sequence);
/**
* @notice Requests a previously published delivery instruction to be redelivered
*
*
* This function must be called with `msg.value` equal to
* quoteDeliveryPrice(targetChain, newReceiverValue, newEncodedExecutionParameters, newDeliveryProviderAddress)
*
* @param deliveryVaaKey VaaKey identifying the wormhole message containing the
* previously published delivery instructions
* @param targetChain The target chain that the original delivery targeted. Must match targetChain from original delivery instructions
* @param newReceiverValue new msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
* @param newEncodedExecutionParameters new encoded information on how to execute delivery that may impact pricing
* e.g. for version EVM_V1, this is a struct that encodes the `gasLimit` with which to call `targetAddress`
* @param newDeliveryProviderAddress The address of the desired delivery provider's implementation of IDeliveryProvider
* @return sequence sequence number of published VAA containing redelivery instructions
*
* @notice *** This will only be able to succeed if the following is true **
* - (For EVM_V1) newGasLimit >= gas limit of the old instruction
* - newReceiverValue >= receiver value of the old instruction
* - (For EVM_V1) newDeliveryProvider's `targetChainRefundPerGasUnused` >= old relay provider's `targetChainRefundPerGasUnused`
*/
function resend(
VaaKey memory deliveryVaaKey,
uint16 targetChain,
uint256 newReceiverValue,
bytes memory newEncodedExecutionParameters,
address newDeliveryProviderAddress
) external payable returns (uint64 sequence);
/**
* @notice Returns the price to request a relay to chain `targetChain`, using the default delivery provider
*
* @param targetChain in Wormhole Chain ID format
* @param receiverValue msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
* @param gasLimit gas limit with which to call `targetAddress`.
* @return nativePriceQuote Price, in units of current chain currency, that the delivery provider charges to perform the relay
* @return targetChainRefundPerGasUnused amount of target chain currency that will be refunded per unit of gas unused,
* if a refundAddress is specified
*/
function quoteEVMDeliveryPrice(
uint16 targetChain,
uint256 receiverValue,
uint256 gasLimit
) external view returns (uint256 nativePriceQuote, uint256 targetChainRefundPerGasUnused);
/**
* @notice Returns the price to request a relay to chain `targetChain`, using delivery provider `deliveryProviderAddress`
*
* @param targetChain in Wormhole Chain ID format
* @param receiverValue msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
* @param gasLimit gas limit with which to call `targetAddress`.
* @param deliveryProviderAddress The address of the desired delivery provider's implementation of IDeliveryProvider
* @return nativePriceQuote Price, in units of current chain currency, that the delivery provider charges to perform the relay
* @return targetChainRefundPerGasUnused amount of target chain currency that will be refunded per unit of gas unused,
* if a refundAddress is specified
*/
function quoteEVMDeliveryPrice(
uint16 targetChain,
uint256 receiverValue,
uint256 gasLimit,
address deliveryProviderAddress
) external view returns (uint256 nativePriceQuote, uint256 targetChainRefundPerGasUnused);
/**
* @notice Returns the price to request a relay to chain `targetChain`, using delivery provider `deliveryProviderAddress`
*
* @param targetChain in Wormhole Chain ID format
* @param receiverValue msg.value that delivery provider should pass in for call to `targetAddress` (in targetChain currency units)
* @param encodedExecutionParameters encoded information on how to execute delivery that may impact pricing
* e.g. for version EVM_V1, this is a struct that encodes the `gasLimit` with which to call `targetAddress`
* @param deliveryProviderAddress The address of the desired delivery provider's implementation of IDeliveryProvider
* @return nativePriceQuote Price, in units of current chain currency, that the delivery provider charges to perform the relay
* @return encodedExecutionInfo encoded information on how the delivery will be executed
* e.g. for version EVM_V1, this is a struct that encodes the `gasLimit` and `targetChainRefundPerGasUnused`
* (which is the amount of target chain currency that will be refunded per unit of gas unused,
* if a refundAddress is specified)
*/
function quoteDeliveryPrice(
uint16 targetChain,
uint256 receiverValue,
bytes memory encodedExecutionParameters,
address deliveryProviderAddress
) external view returns (uint256 nativePriceQuote, bytes memory encodedExecutionInfo);
/**
* @notice Returns the (extra) amount of target chain currency that `targetAddress`
* will be called with, if the `paymentForExtraReceiverValue` field is set to `currentChainAmount`
*
* @param targetChain in Wormhole Chain ID format
* @param currentChainAmount The value that `paymentForExtraReceiverValue` will be set to
* @param deliveryProviderAddress The address of the desired delivery provider's implementation of IDeliveryProvider
* @return targetChainAmount The amount such that if `targetAddress` will be called with `msg.value` equal to
* receiverValue + targetChainAmount
*/
function quoteNativeForChain(
uint16 targetChain,
uint256 currentChainAmount,
address deliveryProviderAddress
) external view returns (uint256 targetChainAmount);
/**
* @notice Returns the address of the current default delivery provider
* @return deliveryProvider The address of (the default delivery provider)'s contract on this source
* chain. This must be a contract that implements IDeliveryProvider.
*/
function getDefaultDeliveryProvider() external view returns (address deliveryProvider);
}
/**
* @title IWormholeRelayerDelivery
* @notice The interface to execute deliveries. Only relevant for Delivery Providers
*/
interface IWormholeRelayerDelivery is IWormholeRelayerBase {
enum DeliveryStatus {
SUCCESS,
RECEIVER_FAILURE
}
enum RefundStatus {
REFUND_SENT,
REFUND_FAIL,
CROSS_CHAIN_REFUND_SENT,
CROSS_CHAIN_REFUND_FAIL_PROVIDER_NOT_SUPPORTED,
CROSS_CHAIN_REFUND_FAIL_NOT_ENOUGH
}
/**
* @custom:member recipientContract - The target contract address
* @custom:member sourceChain - The chain which this delivery was requested from (in wormhole
* ChainID format)
* @custom:member sequence - The wormhole sequence number of the delivery VAA on the source chain
* corresponding to this delivery request
* @custom:member deliveryVaaHash - The hash of the delivery VAA corresponding to this delivery
* request
* @custom:member gasUsed - The amount of gas that was used to call your target contract
* @custom:member status:
* - RECEIVER_FAILURE, if the target contract reverts
* - SUCCESS, if the target contract doesn't revert
* @custom:member additionalStatusInfo:
* - If status is SUCCESS, then this is empty.
* - If status is RECEIVER_FAILURE, this is `RETURNDATA_TRUNCATION_THRESHOLD` bytes of the
* return data (i.e. potentially truncated revert reason information).
* @custom:member refundStatus - Result of the refund. REFUND_SUCCESS or REFUND_FAIL are for
* refunds where targetChain=refundChain; the others are for targetChain!=refundChain,
* where a cross chain refund is necessary
* @custom:member overridesInfo:
* - If not an override: empty bytes array
* - Otherwise: An encoded `DeliveryOverride`
*/
event Delivery(
address indexed recipientContract,
uint16 indexed sourceChain,
uint64 indexed sequence,
bytes32 deliveryVaaHash,
DeliveryStatus status,
uint256 gasUsed,
RefundStatus refundStatus,
bytes additionalStatusInfo,
bytes overridesInfo
);
/**
* @notice The delivery provider calls `deliver` to relay messages as described by one delivery instruction
*
* The delivery provider must pass in the specified (by VaaKeys[]) signed wormhole messages (VAAs) from the source chain
* as well as the signed wormhole message with the delivery instructions (the delivery VAA)
*
* The messages will be relayed to the target address (with the specified gas limit and receiver value) iff the following checks are met:
* - the delivery VAA has a valid signature
* - the delivery VAA's emitter is one of these WormholeRelayer contracts
* - the delivery provider passed in at least enough of this chain's currency as msg.value (enough meaning the maximum possible refund)
* - the instruction's target chain is this chain
* - the relayed signed VAAs match the descriptions in container.messages (the VAA hashes match, or the emitter address, sequence number pair matches, depending on the description given)
*
* @param encodedVMs - An array of signed wormhole messages (all from the same source chain
* transaction)
* @param encodedDeliveryVAA - Signed wormhole message from the source chain's WormholeRelayer
* contract with payload being the encoded delivery instruction container
* @param relayerRefundAddress - The address to which any refunds to the delivery provider
* should be sent
* @param deliveryOverrides - Optional overrides field which must be either an empty bytes array or
* an encoded DeliveryOverride struct
*/
function deliver(
bytes[] memory encodedVMs,
bytes memory encodedDeliveryVAA,
address payable relayerRefundAddress,
bytes memory deliveryOverrides
) external payable;
}
interface IWormholeRelayer is IWormholeRelayerDelivery, IWormholeRelayerSend {}
/*
* Errors thrown by IWormholeRelayer contract
*/
// Bound chosen by the following formula: `memoryWord * 4 + selectorSize`.
// This means that an error identifier plus four fixed size arguments should be available to developers.
// In the case of a `require` revert with error message, this should provide 2 memory word's worth of data.
uint256 constant RETURNDATA_TRUNCATION_THRESHOLD = 132;
//When msg.value was not equal to `delivery provider's quoted delivery price` + `paymentForExtraReceiverValue`
error InvalidMsgValue(uint256 msgValue, uint256 totalFee);
error RequestedGasLimitTooLow();
error DeliveryProviderDoesNotSupportTargetChain(address relayer, uint16 chainId);
error DeliveryProviderCannotReceivePayment();
error DeliveryProviderDoesNotSupportMessageKeyType(uint8 keyType);
//When calling `delivery()` a second time even though a delivery is already in progress
error ReentrantDelivery(address msgSender, address lockedBy);
error InvalidPayloadId(uint8 parsed, uint8 expected);
error InvalidPayloadLength(uint256 received, uint256 expected);
error InvalidVaaKeyType(uint8 parsed);
error TooManyMessageKeys(uint256 numMessageKeys);
error InvalidDeliveryVaa(string reason);
//When the delivery VAA (signed wormhole message with delivery instructions) was not emitted by the
// registered WormholeRelayer contract
error InvalidEmitter(bytes32 emitter, bytes32 registered, uint16 chainId);
error MessageKeysLengthDoesNotMatchMessagesLength(uint256 keys, uint256 vaas);
error VaaKeysDoNotMatchVaas(uint8 index);
//When someone tries to call an external function of the WormholeRelayer that is only intended to be
// called by the WormholeRelayer itself (to allow retroactive reverts for atomicity)
error RequesterNotWormholeRelayer();
//When trying to relay a `DeliveryInstruction` to any other chain but the one it was specified for
error TargetChainIsNotThisChain(uint16 targetChain);
//When a `DeliveryOverride` contains a gas limit that's less than the original
error InvalidOverrideGasLimit();
//When a `DeliveryOverride` contains a receiver value that's less than the original
error InvalidOverrideReceiverValue();
//When a `DeliveryOverride` contains a 'refund per unit of gas unused' that's less than the original
error InvalidOverrideRefundPerGasUnused();
//When the delivery provider doesn't pass in sufficient funds (i.e. msg.value does not cover the
// maximum possible refund to the user)
error InsufficientRelayerFunds(uint256 msgValue, uint256 minimum);
//When a bytes32 field can't be converted into a 20 byte EVM address, because the 12 padding bytes
// are non-zero (duplicated from Utils.sol)
error NotAnEvmAddress(bytes32);
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev An operation with an ERC20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data);
if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import {TokenSender} from "@wormhole-upgradeable/WormholeRelayerSDK.sol";
import {CCTPSender, CCTPBase} from "@wormhole-upgradeable/CCTPBase.sol";
import {VaaKey} from "@wormhole-upgradeable/interfaces/IWormholeRelayer.sol";
import "@wormhole-upgradeable/Utils.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../HubSpokeStructs.sol";
import "./SpokeGetters.sol";
import "./SpokeUtilities.sol";
import "../wormhole/TokenReceiverWithCCTP.sol";
/**
* @title Spoke
* @notice The Spoke contract is the point of entry for cross-chain actions; users initiate an action by calling any of
* the `public payable` functions (ex: `#depositCollateral`, `#withdrawCollateral`) with their desired asset and amount,
* and using Wormhole we send the payload/tokens to the Hub on the target chain; if the action concludes with sending
* tokens back to the user, we receive the final payload/tokens from the Hub before sending tokens to the user. This
* contract also implements wormhole's CCTP contracts to send/receive USDC.
*/
contract Spoke is HubSpokeStructs, SpokeGetters, SpokeUtilities, TokenSender, CCTPSender, TokenReceiverWithCCTP, Ownable {
using SafeERC20 for IERC20;
/**
* @notice Spoke constructor - Initializes a new spoke with given parameters
*
* @param chainId: Chain ID of the chain that this Spoke is deployed on
* @param wormhole: Address of the Wormhole contract on this Spoke chain
* @param tokenBridge: Address of the TokenBridge contract on this Spoke chain
* @param relayer: Address of the WormholeRelayer contract on this Spoke chain
* @param hubChainId: Chain ID of the Hub
* @param hubContractAddress: Contract address of the Hub contract (on the Hub chain)
* @param _circleMessageTransmitter: Cicle Message Transmitter contract (cctp)
* @param _circleTokenMessenger: Cicle Token Messenger contract (cctp)
* @param _USDC: USDC token contract (cctp)
*/
constructor(
uint16 chainId,
address wormhole,
address tokenBridge,
address relayer,
uint16 hubChainId,
address hubContractAddress,
address _circleMessageTransmitter,
address _circleTokenMessenger,
address _USDC
) Ownable(msg.sender) {
CCTPBase.__CCTPBase_init(
relayer,
tokenBridge,
wormhole,
_circleMessageTransmitter,
_circleTokenMessenger,
_USDC
);
_state.chainId = chainId;
_state.hubChainId = hubChainId;
_state.hubContractAddress = hubContractAddress;
_state.defaultGasLimitRoundtrip = 650_000;
_state.isUsingCCTP = _circleMessageTransmitter != address(0); // zero address would indicate not using/supported
setRegisteredSender(hubChainId, toWormholeFormat(hubContractAddress));
// disable the registrationOwner so that the Hub can't be changed.
registrationOwner = address(0);
}
/**
* @notice Allows the contract deployer to set the default gas limit used in wormhole relay quotes
*
* @param value: the new value for `defaultGasLimitRoundtrip`
*/
function setDefaultGasLimitRoundtrip(uint256 value) external onlyOwner {
_state.defaultGasLimitRoundtrip = value;
}
/**
* @notice Allows the contract deployer to toggle whether we are using CCTP for USDC
* NOTE: If `_circleMessageTransmitter` is the null address, it indicates CCTP is not supported on this chain, thus
* we don't do anything.
*
* @param value: the new value for `isUsingCCTP`
*/
function setIsUsingCCTP(bool value) external onlyOwner {
if (address(circleMessageTransmitter) == address(0)) return; // zero address would indicate not using/supported
_state.isUsingCCTP = value;
}
/**
* @notice Allows the caller to initiate a cross-chain deposit. The caller must have approved the `assetAmount` of
* `asset` and must have provided enough `msg.value` to cover the relay
*
* @param asset: Addresss of the asset to deposit
* @param assetAmount: Amount of the asset to deposit
* @param costForReturnDelivery: The quoted cost for return delivery from the Hub (for refunds)
* @return sequence number of the message sent.
*/
function depositCollateral(address asset, uint256 assetAmount, uint256 costForReturnDelivery)
public
payable
returns (uint64 sequence)
{
require(msg.value >= getDeliveryCostRoundtrip(costForReturnDelivery, true), "Insufficient value sent");
sequence = _doAction(Action.Deposit, asset, assetAmount, costForReturnDelivery, false);
}
/**
* @notice Allows the caller to initiate a cross-chain withdraw. The caller must have provided enough `msg.value`
* to cover the relay and the return delivery
*
* @param asset: Addresss of the asset to withdraw
* @param assetAmount: Amount of the asset to withdraw
* @param costForReturnDelivery: The quoted cost for return delivery from the Hub
* @return sequence number of the message sent.
*/
function withdrawCollateral(address asset, uint256 assetAmount, uint256 costForReturnDelivery)
public
payable
returns (uint64 sequence)
{
require(costForReturnDelivery > 0, "Non-zero costForReturnDelivery");
sequence = _doAction(Action.Withdraw, asset, assetAmount, costForReturnDelivery, false);
}
/**
* @notice Allows the caller to initiate a cross-chain borrow. The caller must have provided enough `msg.value`
* to cover the relay and the return delivery
*
* @param asset: Addresss of the asset to borrow
* @param assetAmount: Amount of the asset to borrow
* @param costForReturnDelivery: The quoted cost for return delivery from the Hub
* @return sequence number of the message sent.
*/
function borrow(address asset, uint256 assetAmount, uint256 costForReturnDelivery)
public
payable
returns (uint64 sequence)
{
require(costForReturnDelivery > 0, "Non-zero costForReturnDelivery");
sequence = _doAction(Action.Borrow, asset, assetAmount, costForReturnDelivery, false);
}
/**
* @notice Allows the caller to initiate a cross-chain repay. The caller must have approved the `assetAmount` of
* `asset` and must have provided enough `msg.value` to cover the relay
*
* @param asset: Addresss of the asset to borrow
* @param assetAmount: Amount of the asset to borrow
* @param costForReturnDelivery: The quoted cost for return delivery from the Hub (for refunds)
* @return sequence number of the message sent.
*/
function repay(address asset, uint256 assetAmount, uint256 costForReturnDelivery)
public
payable
returns (uint64 sequence)
{
require(msg.value >= getDeliveryCostRoundtrip(costForReturnDelivery, true), "Insufficient value sent");
sequence = _doAction(Action.Repay, asset, assetAmount, costForReturnDelivery, false);
}
/**
* @notice Allows the caller to initiate a cross-chain deposit with native tokens. The caller must have provided
* enough `msg.value` to cover the relay+return and their desired deposit amount
* @param costForReturnDelivery: The quoted cost for return delivery from the Hub (for refunds)
* enough `msg.value` to cover the relay and their desired deposit amount
* @return sequence number of the message sent.
*/
function depositCollateralNative(uint256 costForReturnDelivery) public payable returns (uint64 sequence) {
uint256 totalCost = getDeliveryCostRoundtrip(costForReturnDelivery, true);
require(msg.value >= totalCost, "Spoke::depositCollateralNative:Insufficient value sent");
uint256 amount = msg.value - totalCost;
sequence = _doAction(Action.DepositNative, address(0), amount, costForReturnDelivery, false);
}
/**
* @notice Allows the caller to initiate a cross-chain repay with native tokens. The caller must have provided
* enough `msg.value` to cover the relay+return and their desired repay amount
* @param costForReturnDelivery: The quoted cost for return delivery from the Hub (for refunds)
* enough `msg.value` to cover the relay and their desired repay amount
* @return sequence number of the message sent.
*/
function repayNative(uint256 costForReturnDelivery) public payable returns (uint64 sequence) {
uint256 totalCost = getDeliveryCostRoundtrip(costForReturnDelivery, true);
require(msg.value >= totalCost, "Spoke::repayNative:Insufficient value sent");
uint256 amount = msg.value - totalCost;
sequence = _doAction(Action.RepayNative, address(0), amount, costForReturnDelivery, false);
}
/**
* @notice Allows the caller to initiate a cross-chain withdraw with native tokens. The caller must have provided
* enough `msg.value` to cover the relay and the return delivery
*
* @param assetAmount: Amount of the asset to withdraw
* @param costForReturnDelivery: The quoted cost for return delivery from the Hub
* @param unwrap: Whether to unwrap the native tokens or not
* @return sequence number of the message sent.
*/
function withdrawCollateralNative(uint256 assetAmount, uint256 costForReturnDelivery, bool unwrap)
public
payable
returns (uint64 sequence)
{
sequence = _doAction(Action.Withdraw, address(tokenBridge.WETH()), assetAmount, costForReturnDelivery, unwrap);
}
/**
* @notice Allows the caller to initiate a cross-chain borrow with native tokens. The caller must have provided
* enough `msg.value` to cover the relay and the return delivery
*
* @param assetAmount: Amount of the asset to borrow
* @param costForReturnDelivery: The quoted cost for return delivery from the Hub
* @param unwrap: Whether to unwrap the native tokens or not
* @return sequence number of the message sent.
*/
function borrowNative(uint256 assetAmount, uint256 costForReturnDelivery, bool unwrap)
public
payable
returns (uint64 sequence)
{
sequence = _doAction(Action.Borrow, address(tokenBridge.WETH()), assetAmount, costForReturnDelivery, unwrap);
}
/**
* @notice Get the quote for the wormhole delivery cost, accounting for a forward() call on the Hub (in case of potential
* reverts or to receive tokens on borrow/withdraw)
*
* @param costForReturnDelivery: the result of Hub#getCostForReturnDelivery()
* @param withTokenTransfer: whether to include the message fee for a token bridge transfer (on deposit or repay)
* @return cost for the forward() call on the Hub
*/
function getDeliveryCostRoundtrip(uint256 costForReturnDelivery, bool withTokenTransfer)
public
view
returns (uint256)
{
(uint256 cost,) =
wormholeRelayer.quoteEVMDeliveryPrice(hubChainId(), costForReturnDelivery, defaultGasLimitRoundtrip());
if (withTokenTransfer) {
return cost + tokenBridge.wormhole().messageFee();
}
return cost;
}
/**
* @dev Initiates an action (deposit, borrow, withdraw, or repay) on the spoke by sending
* a Wormhole message (potentially a TokenBridge message with tokens) to the Hub
* @param action - the action to be performed. It can be Deposit, Borrow, Withdraw, Repay, DepositNative, RepayNative.
* @param asset - the address of the relevant asset. For native tokens like ETH, AVAX, this will be the zero address.
* @param assetAmount - the amount of the asset to be involved in the action.
* @param costForReturnDelivery - the cost to forward tokens back from the Hub
* @param unwrap - a boolean value indicating whether to unwrap the asset or not.
* @return sequence number of the message sent.
*/
function _doAction(Action action, address asset, uint256 assetAmount, uint256 costForReturnDelivery, bool unwrap)
internal
returns (uint64 sequence)
{
require(assetAmount > 0, "No zero asset amount");
bool withCCTP = asset == USDC && _state.isUsingCCTP;
// for token transfers, only validate amount if we're using the token bridge
if (asset != address(0) && !withCCTP) {
requireAssetAmountValidForTokenBridge(asset, assetAmount);
}
Action hubAction = action;
if (action == Action.DepositNative) {
hubAction = Action.Deposit;
} else if (action == Action.RepayNative) {
hubAction = Action.Repay;
}
bool sendingTokens = action == Action.Deposit || action == Action.Repay;
bool sendingUSDC = withCCTP && sendingTokens;
bool receivingUSDC = withCCTP && !sendingTokens;
bytes memory userPayload = abi.encode(uint8(hubAction), msg.sender, asset, unwrap, sendingUSDC, receivingUSDC);
bytes memory payload = sendingUSDC
? userPayload
: abi.encode(assetAmount, userPayload); // encoding again so it's the same format as cctp messages
if (sendingTokens) {
sequence = withCCTP
? _sendUSDCWithPayload(payload, assetAmount, costForReturnDelivery)
: _sendTokenBridgeMessage(payload, asset, assetAmount, costForReturnDelivery);
} else if (action == Action.Withdraw || action == Action.Borrow) {
sequence = wormholeRelayer.sendPayloadToEvm{value: msg.value}(
hubChainId(),
hubContractAddress(),
payload,
costForReturnDelivery,
defaultGasLimitRoundtrip(),
chainId(), // refundChain
msg.sender // refundAddress
);
} else if (action == Action.DepositNative || action == Action.RepayNative) {
sequence = _sendTokenBridgeMessageNative(payload, assetAmount, costForReturnDelivery);
}
}
/**
* @dev Sends EVM Worlhole Relayer a message with the given payload and value
* @param value - amount of ETH to be sent to relayer
* @param payload - the payload to be sent
* @param costForReturnDelivery - the cost to forward tokens back from the Hub
* @param refundAddress The address on `refundChain` to deliver any refund to
* @return sequence - the sequence number of the message sent
*/
function _sendToEvmWormHoleRelayer(
uint256 value,
bytes memory payload,
uint256 costForReturnDelivery,
address refundAddress
) internal returns(uint64 sequence) {
return wormholeRelayer.sendPayloadToEvm{value: value}(
hubChainId(),
hubContractAddress(),
payload,
costForReturnDelivery,
defaultGasLimitRoundtrip(),
hubChainId(),
refundAddress
);
}
/**
* @dev Sends a TokenBridge message with the given payload, asset, and amount
* @param payload - the payload to be sent
* @param asset - the address of the asset to be sent
* @param amount - the amount of the asset to be sent
* @param costForReturnDelivery - the cost to forward tokens back from the Hub
* @return sequence - the sequence number of the message sent
*/
function _sendTokenBridgeMessage(bytes memory payload, address asset, uint256 amount, uint256 costForReturnDelivery)
internal
returns (uint64)
{
IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);
return sendTokenWithPayloadToEvm(
hubChainId(),
hubContractAddress(),
payload,
costForReturnDelivery,
defaultGasLimitRoundtrip(),
asset,
amount,
chainId(), // refundChain
msg.sender // refundAddress
);
}
/**
* @dev Sends a TokenBridge message with the given payload and amount in native tokens
* @param payload - the payload to be sent
* @param amount - the amount of native tokens to be sent
* @param costForReturnDelivery - the cost to forward tokens back from the Hub
* @return sequence - the sequence number of the message sent
*/
function _sendTokenBridgeMessageNative(bytes memory payload, uint256 amount, uint256 costForReturnDelivery)
internal
returns (uint64)
{
uint256 amountPlusFee = amount + tokenBridge.wormhole().messageFee();
uint64 sequence = tokenBridge.wrapAndTransferETHWithPayload{value: amountPlusFee}(
hubChainId(), toWormholeFormat(hubContractAddress()), 0, payload
);
VaaKey[] memory additionalVaas = new VaaKey[](1);
additionalVaas[0] = VaaKey(chainId(), toWormholeFormat(address(tokenBridge)), sequence);
uint256 deliveryCost = getDeliveryCostRoundtrip(costForReturnDelivery, false);
return wormholeRelayer.sendVaasToEvm{value: deliveryCost}(
hubChainId(),
hubContractAddress(),
payload,
costForReturnDelivery,
defaultGasLimitRoundtrip(),
additionalVaas,
chainId(), // refundChain
msg.sender // refundAddress
);
}
/**
* @dev Sends USDC with the given payload via wormhole's CCTP integration
* @param payload - the payload to be sent
* @param amount - the amount of the asset to be sent
* @param costForReturnDelivery - the cost to forward tokens back from the Hub
* @return sequence - the sequence number of the message sent
*/
function _sendUSDCWithPayload(bytes memory payload, uint256 amount, uint256 costForReturnDelivery)
internal
returns (uint64)
{
IERC20(USDC).safeTransferFrom(msg.sender, address(this), amount);
return sendUSDCWithPayloadToEvm(
hubChainId(),
hubContractAddress(),
payload,
costForReturnDelivery,
defaultGasLimitRoundtrip(),
amount,
chainId(), // refundChain
msg.sender // refundAddress
);
}
/**
* @dev Returns whether we are using CCTP while receiving wormhole messages, as specified in the encoded `payload`
* @param payload - the payload received
*/
function messageWithCCTP(bytes memory payload) internal pure override returns (bool) {
(,, bool withCCTP) = _decodePayload(payload);
// NOTE: we are not checking _state.isUsingCCTP here in order to handle it as best effort
return withCCTP;
}
function receiveWormholeMessages(
bytes memory payload,
bytes[] memory additionalVaas,
bytes32 sourceAddress,
uint16 sourceChain,
bytes32 deliveryHash
) public virtual override(TokenReceiverWithCCTP) payable {
if (additionalVaas.length == 0) {
(address recipient,,) = _decodePayload(payload);
// send any refund back to the recipient
if (msg.value > 0) {
(bool refundSuccess,) = recipient.call{value: msg.value}("");
require(refundSuccess, "refund failed");
}
} else {
super.receiveWormholeMessages(payload, additionalVaas, sourceAddress, sourceChain, deliveryHash);
}
}
/**
* @dev Receives a payload and tokens, and processes them
* @param payload - the payload received
* @param receivedTokens - the tokens received
* @param sourceAddress - the source address of the tokens
* @param sourceChain - the source chain of the tokens
* @param deliveryHash - the delivery hash of the tokens
*/
function receivePayloadAndTokens(
bytes memory payload,
TokenReceived[] memory receivedTokens,
bytes32 sourceAddress,
uint16 sourceChain,
bytes32 deliveryHash
)
internal
override
onlyWormholeRelayer
isRegisteredSender(sourceChain, sourceAddress)
replayProtect(deliveryHash)
{
require(receivedTokens.length == 1, "Expecting one transfer");
TokenReceived memory receivedToken = receivedTokens[0];
(address recipient, bool unwrap,) = _decodePayload(payload);
if (unwrap) {
// unwrap and transfer to recipient
tokenBridge.WETH().withdraw(receivedToken.amount);
(bool withdrawSuccess,) = recipient.call{value: receivedToken.amount}("");
require(withdrawSuccess, "withdraw to native failed");
} else {
IERC20(receivedToken.tokenAddress).safeTransfer(recipient, receivedToken.amount);
}
// send any refund back to the recipient
if (msg.value > 0) {
(bool refundSuccess,) = recipient.call{value: msg.value}("");
require(refundSuccess, "refund failed");
}
}
/**
* @dev Receives a payload and USDC via CCTP
* @param userPayload - the payload received
* @param amountUSDCReceived - the amount of USDC received
* @param sourceAddress - the source address of the tokens
* @param sourceChain - the source chain of the tokens
* @param deliveryHash - the delivery hash of the tokens
*/
function receivePayloadAndUSDC(
bytes memory userPayload,
uint256 amountUSDCReceived,
bytes32 sourceAddress,
uint16 sourceChain,
bytes32 deliveryHash
)
internal
override
onlyWormholeRelayer
isRegisteredSender(sourceChain, sourceAddress)
replayProtect(deliveryHash)
{
(address recipient,,) = abi.decode(userPayload, (address, bool, bool));
IERC20(USDC).safeTransfer(recipient, amountUSDCReceived);
// send any refund back to the recipient
if (msg.value > 0) {
(bool refundSuccess,) = recipient.call{value: msg.value}("");
require(refundSuccess, "refund failed");
}
}
/**
* @dev Decodes `payload` into expected arguments
* @param payload - the payload received
*/
function _decodePayload(
bytes memory payload
) internal pure returns (address recipient, bool unwrap, bool withCCTP) {
(, bytes memory userPayload) = abi.decode(payload, (uint256, bytes));
(recipient, unwrap, withCCTP) = abi.decode(userPayload, (address, bool, bool));
}
/**
* @notice fallback function to receive unwrapped native asset
*/
fallback() external payable {}
/**
* @notice Function to receive ETH
*/
receive() external payable {}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "./SpokeState.sol";
/**
* @title SpokeGetters
* @notice A set of public getter functions
*/
contract SpokeGetters is SpokeState {
function chainId() public view returns (uint16) {
return _state.chainId;
}
function consistencyLevel() public view returns (uint8) {
return _state.consistencyLevel;
}
function hubChainId() public view returns (uint16) {
return _state.hubChainId;
}
function hubContractAddress() public view returns (address) {
return _state.hubContractAddress;
}
function defaultGasLimitRoundtrip() public view returns (uint256) {
return _state.defaultGasLimitRoundtrip;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "../HubSpokeStructs.sol";
/**
* @title SpokeStorage
* @notice Contract defining state variables for the Spoke contract
*/
contract SpokeStorage is HubSpokeStructs {
struct State {
uint16 chainId;
// number of confirmations for wormhole messages
uint8 consistencyLevel;
uint16 hubChainId;
address hubContractAddress;
uint256 defaultGasLimitRoundtrip;
bool isUsingCCTP;
// @dev storage gap
uint256[50] ______gap;
}
}
/**
* @title SpokeState
* @notice Contract holding state variable for the Spoke contract
*/
contract SpokeState {
SpokeStorage.State _state;
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "../../interfaces/IERC20decimals.sol";
/**
* @title SpokeUtilities
* @notice A set of internal utility functions
*/
contract SpokeUtilities {
/**
* @dev This function checks if the asset amount is valid for the token bridge
* @param assetAddress The address of the asset
* @param assetAmount The amount of the asset
*/
function requireAssetAmountValidForTokenBridge(address assetAddress, uint256 assetAmount) internal view {
uint8 decimals = IERC20decimals(assetAddress).decimals();
require(
deNormalizeAmount(normalizeAmount(assetAmount, decimals), decimals) == assetAmount,
"Too many decimal places"
);
}
/**
* @dev This function normalizes the amount based on the decimals
* @param amount The amount to be normalized
* @param decimals The number of decimals
* @return The normalized amount
*/
function normalizeAmount(uint256 amount, uint8 decimals) internal pure returns (uint256) {
if (decimals > 8) {
amount /= 10 ** (decimals - 8);
} else if (decimals < 8){
amount *= 10 ** (8 - decimals);
}
return amount;
}
/**
* @dev This function denormalizes the amount based on the decimals
* @param amount The amount to be denormalized
* @param decimals The number of decimals
* @return The denormalized amount
*/
function deNormalizeAmount(uint256 amount, uint8 decimals) internal pure returns (uint256) {
if (decimals > 8) {
amount *= 10 ** (decimals - 8);
} else if (decimals < 8) {
amount /= 10 ** (8 - decimals);
}
return amount;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import {TokenReceiver} from "@wormhole-upgradeable/WormholeRelayerSDK.sol";
import {CCTPReceiver} from "@wormhole-upgradeable/CCTPBase.sol";
abstract contract TokenReceiverWithCCTP is CCTPReceiver, TokenReceiver {
/**
* @dev Overriding the superclasses' function to choose whether to use CCTP or not, based on the implemented
* `isUsingCCTP` function
* @param payload - the payload received
* @param additionalVaas - any wormhole VAAs received
* @param sourceAddress - the source address of the tokens
* @param sourceChain - the source chain of the tokens
* @param deliveryHash - the delivery hash of the tokens
*/
function receiveWormholeMessages(
bytes memory payload,
bytes[] memory additionalVaas,
bytes32 sourceAddress,
uint16 sourceChain,
bytes32 deliveryHash
) public virtual override(TokenReceiver, CCTPReceiver) payable {
if (messageWithCCTP(payload)) {
_receiveWormholeMessagesWithCCTP(payload, additionalVaas, sourceAddress, sourceChain, deliveryHash);
} else {
_receiveWormholeMessages(payload, additionalVaas, sourceAddress, sourceChain, deliveryHash);
}
}
/**
* @dev Virtual function to decode `payload` and determine if using CCTP or not
* @param payload - the payload received
*/
function messageWithCCTP(bytes memory payload) internal view virtual returns (bool);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "./interfaces/IWormholeRelayer.sol";
function toWormholeFormat(address addr) pure returns (bytes32) {
return bytes32(uint256(uint160(addr)));
}
function fromWormholeFormat(bytes32 whFormatAddress) pure returns (address) {
if (uint256(whFormatAddress) >> 160 != 0) {
revert NotAnEvmAddress(whFormatAddress);
}
return address(uint160(uint256(whFormatAddress)));
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./interfaces/IWormholeReceiver.sol";
import "./interfaces/IWormholeRelayer.sol";
import "./interfaces/ITokenBridge.sol";
import "./Utils.sol";
abstract contract Base {
IWormholeRelayer public wormholeRelayer;
IWormhole public wormhole;
mapping(bytes32 => bool) public seenDeliveryVaaHashes;
address registrationOwner;
mapping(uint16 => bytes32) registeredSenders;
bool internal _wormholeRelayerInitialized;
function __Base_init(address _wormholeRelayer, address _wormhole) public {
require(!_wormholeRelayerInitialized, "WRI");
_wormholeRelayerInitialized = true;
wormholeRelayer = IWormholeRelayer(_wormholeRelayer);
wormhole = IWormhole(_wormhole);
registrationOwner = msg.sender;
}
modifier onlyWormholeRelayer() {
require(msg.sender == address(wormholeRelayer), "Msg.sender is not Wormhole Relayer");
_;
}
modifier replayProtect(bytes32 deliveryHash) {
require(!seenDeliveryVaaHashes[deliveryHash], "Message already processed");
seenDeliveryVaaHashes[deliveryHash] = true;
_;
}
modifier isRegisteredSender(uint16 sourceChain, bytes32 sourceAddress) {
require(registeredSenders[sourceChain] == sourceAddress, "Not registered sender");
_;
}
/**
* Sets the registered address for 'sourceChain' to 'sourceAddress'
* So that for messages from 'sourceChain', only ones from 'sourceAddress' are valid
*
* Assumes only one sender per chain is valid
* Sender is the address that called 'send' on the Wormhole Relayer contract on the source chain)
*/
function setRegisteredSender(uint16 sourceChain, bytes32 sourceAddress) public {
require(msg.sender == registrationOwner, "Not allowed to set registered sender");
registeredSenders[sourceChain] = sourceAddress;
}
}
abstract contract TokenBase is Base {
ITokenBridge public tokenBridge;
function __TokenBase_init(address _wormholeRelayer, address _tokenBridge, address _wormhole) public {
require(!_wormholeRelayerInitialized, "WRI");
Base.__Base_init(_wormholeRelayer, _wormhole);
tokenBridge = ITokenBridge(_tokenBridge);
}
function getDecimals(address tokenAddress) internal view returns (uint8 decimals) {
// query decimals
(, bytes memory queriedDecimals) = address(tokenAddress).staticcall(abi.encodeWithSignature("decimals()"));
decimals = abi.decode(queriedDecimals, (uint8));
}
function getTokenAddressOnThisChain(uint16 tokenHomeChain, bytes32 tokenHomeAddress)
internal
view
returns (address tokenAddressOnThisChain)
{
return tokenHomeChain == wormhole.chainId()
? fromWormholeFormat(tokenHomeAddress)
: tokenBridge.wrappedAsset(tokenHomeChain, tokenHomeAddress);
}
}
abstract contract TokenSender is TokenBase {
/**
* transferTokens wraps common boilerplate for sending tokens to another chain using IWormholeRelayer
* - approves tokenBridge to spend 'amount' of 'token'
* - emits token transfer VAA
* - returns VAA key for inclusion in WormholeRelayer `additionalVaas` argument
*
* Note: this function uses transferTokensWithPayload instead of transferTokens since the former requires that only the targetAddress
* can redeem transfers. Otherwise it's possible for another address to redeem the transfer before the targetContract is invoked by
* the offchain relayer and the target contract would have to be hardened against this.
*
*/
function transferTokens(address token, uint256 amount, uint16 targetChain, address targetAddress)
internal
returns (VaaKey memory)
{
return transferTokens(token, amount, targetChain, targetAddress, bytes(""));
}
/**
* transferTokens wraps common boilerplate for sending tokens to another chain using IWormholeRelayer.
* A payload can be included in the transfer vaa. By including a payload here instead of the deliveryVaa,
* fewer trust assumptions are placed on the WormholeRelayer contract.
*
* - approves tokenBridge to spend 'amount' of 'token'
* - emits token transfer VAA
* - returns VAA key for inclusion in WormholeRelayer `additionalVaas` argument
*
* Note: this function uses transferTokensWithPayload instead of transferTokens since the former requires that only the targetAddress
* can redeem transfers. Otherwise it's possible for another address to redeem the transfer before the targetContract is invoked by
* the offchain relayer and the target contract would have to be hardened against this.
*/
function transferTokens(
address token,
uint256 amount,
uint16 targetChain,
address targetAddress,
bytes memory payload
) internal returns (VaaKey memory) {
SafeERC20.forceApprove(IERC20(token), address(tokenBridge), amount);
uint64 sequence = tokenBridge.transferTokensWithPayload{value: wormhole.messageFee()}(
token, amount, targetChain, toWormholeFormat(targetAddress), 0, payload
);
return VaaKey({
emitterAddress: toWormholeFormat(address(tokenBridge)),
chainId: wormhole.chainId(),
sequence: sequence
});
}
function sendTokenWithPayloadToEvm(
uint16 targetChain,
address targetAddress,
bytes memory payload,
uint256 receiverValue,
uint256 gasLimit,
address token,
uint256 amount
) internal returns (uint64) {
VaaKey[] memory vaaKeys = new VaaKey[](1);
vaaKeys[0] = transferTokens(token, amount, targetChain, targetAddress);
(uint256 cost,) = wormholeRelayer.quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit);
return wormholeRelayer.sendVaasToEvm{value: cost}(
targetChain, targetAddress, payload, receiverValue, gasLimit, vaaKeys
);
}
function sendTokenWithPayloadToEvm(
uint16 targetChain,
address targetAddress,
bytes memory payload,
uint256 receiverValue,
uint256 gasLimit,
address token,
uint256 amount,
uint16 refundChain,
address refundAddress
) internal returns (uint64) {
VaaKey[] memory vaaKeys = new VaaKey[](1);
vaaKeys[0] = transferTokens(token, amount, targetChain, targetAddress);
(uint256 cost,) = wormholeRelayer.quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit);
return wormholeRelayer.sendVaasToEvm{value: cost}(
targetChain, targetAddress, payload, receiverValue, gasLimit, vaaKeys, refundChain, refundAddress
);
}
}
abstract contract TokenReceiver is TokenBase {
struct TokenReceived {
bytes32 tokenHomeAddress;
uint16 tokenHomeChain;
address tokenAddress; // wrapped address if tokenHomeChain !== this chain, else tokenHomeAddress (in evm address format)
uint256 amount;
uint256 amountNormalized; // if decimals > 8, normalized to 8 decimal places
}
function receiveWormholeMessages(
bytes memory payload,
bytes[] memory additionalVaas,
bytes32 sourceAddress,
uint16 sourceChain,
bytes32 deliveryHash
) external virtual payable {
_receiveWormholeMessages(payload, additionalVaas, sourceAddress, sourceChain, deliveryHash);
}
function _receiveWormholeMessages(
bytes memory payload,
bytes[] memory additionalVaas,
bytes32 sourceAddress,
uint16 sourceChain,
bytes32 deliveryHash
) internal {
TokenReceived[] memory receivedTokens = new TokenReceived[](additionalVaas.length);
for (uint256 i = 0; i < additionalVaas.length; ++i) {
IWormhole.VM memory parsed = wormhole.parseVM(additionalVaas[i]);
require(
parsed.emitterAddress == tokenBridge.bridgeContracts(parsed.emitterChainId), "Not a Token Bridge VAA"
);
ITokenBridge.TransferWithPayload memory transfer = tokenBridge.parseTransferWithPayload(parsed.payload);
require(
transfer.to == toWormholeFormat(address(this)) && transfer.toChain == wormhole.chainId(),
"Token was not sent to this address"
);
tokenBridge.completeTransferWithPayload(additionalVaas[i]);
address thisChainTokenAddress = getTokenAddressOnThisChain(transfer.tokenChain, transfer.tokenAddress);
uint8 decimals = getDecimals(thisChainTokenAddress);
uint256 denormalizedAmount = transfer.amount;
if (decimals > 8) denormalizedAmount *= uint256(10) ** (decimals - 8);
receivedTokens[i] = TokenReceived({
tokenHomeAddress: transfer.tokenAddress,
tokenHomeChain: transfer.tokenChain,
tokenAddress: thisChainTokenAddress,
amount: denormalizedAmount,
amountNormalized: transfer.amount
});
}
// call into overriden method
receivePayloadAndTokens(payload, receivedTokens, sourceAddress, sourceChain, deliveryHash);
}
function receivePayloadAndTokens(
bytes memory payload,
TokenReceived[] memory receivedTokens,
bytes32 sourceAddress,
uint16 sourceChain,
bytes32 deliveryHash
) internal virtual {}
}
{
"compilationTarget": {
"src/contracts/lendingSpoke/Spoke.sol": "Spoke"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs",
"useLiteralContent": true
},
"optimizer": {
"enabled": true,
"runs": 2000
},
"remappings": [],
"viaIR": true
}
[{"inputs":[{"internalType":"uint16","name":"chainId","type":"uint16"},{"internalType":"address","name":"wormhole","type":"address"},{"internalType":"address","name":"tokenBridge","type":"address"},{"internalType":"address","name":"relayer","type":"address"},{"internalType":"uint16","name":"hubChainId","type":"uint16"},{"internalType":"address","name":"hubContractAddress","type":"address"},{"internalType":"address","name":"_circleMessageTransmitter","type":"address"},{"internalType":"address","name":"_circleTokenMessenger","type":"address"},{"internalType":"address","name":"_USDC","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"NotAnEvmAddress","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"USDC","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_wormholeRelayer","type":"address"},{"internalType":"address","name":"_wormhole","type":"address"}],"name":"__Base_init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_wormholeRelayer","type":"address"},{"internalType":"address","name":"_tokenBridge","type":"address"},{"internalType":"address","name":"_wormhole","type":"address"},{"internalType":"address","name":"_circleMessageTransmitter","type":"address"},{"internalType":"address","name":"_circleTokenMessenger","type":"address"},{"internalType":"address","name":"_USDC","type":"address"}],"name":"__CCTPBase_init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_wormholeRelayer","type":"address"},{"internalType":"address","name":"_tokenBridge","type":"address"},{"internalType":"address","name":"_wormhole","type":"address"}],"name":"__TokenBase_init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"assetAmount","type":"uint256"},{"internalType":"uint256","name":"costForReturnDelivery","type":"uint256"}],"name":"borrow","outputs":[{"internalType":"uint64","name":"sequence","type":"uint64"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assetAmount","type":"uint256"},{"internalType":"uint256","name":"costForReturnDelivery","type":"uint256"},{"internalType":"bool","name":"unwrap","type":"bool"}],"name":"borrowNative","outputs":[{"internalType":"uint64","name":"sequence","type":"uint64"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"chainId","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"circleMessageTransmitter","outputs":[{"internalType":"contract IMessageTransmitter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"circleTokenMessenger","outputs":[{"internalType":"contract ITokenMessenger","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"consistencyLevel","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"defaultGasLimitRoundtrip","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"assetAmount","type":"uint256"},{"internalType":"uint256","name":"costForReturnDelivery","type":"uint256"}],"name":"depositCollateral","outputs":[{"internalType":"uint64","name":"sequence","type":"uint64"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"costForReturnDelivery","type":"uint256"}],"name":"depositCollateralNative","outputs":[{"internalType":"uint64","name":"sequence","type":"uint64"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"costForReturnDelivery","type":"uint256"},{"internalType":"bool","name":"withTokenTransfer","type":"bool"}],"name":"getDeliveryCostRoundtrip","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"hubChainId","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"hubContractAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"payload","type":"bytes"},{"internalType":"bytes[]","name":"additionalVaas","type":"bytes[]"},{"internalType":"bytes32","name":"sourceAddress","type":"bytes32"},{"internalType":"uint16","name":"sourceChain","type":"uint16"},{"internalType":"bytes32","name":"deliveryHash","type":"bytes32"}],"name":"receiveWormholeMessages","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"assetAmount","type":"uint256"},{"internalType":"uint256","name":"costForReturnDelivery","type":"uint256"}],"name":"repay","outputs":[{"internalType":"uint64","name":"sequence","type":"uint64"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"costForReturnDelivery","type":"uint256"}],"name":"repayNative","outputs":[{"internalType":"uint64","name":"sequence","type":"uint64"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"seenDeliveryVaaHashes","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"setDefaultGasLimitRoundtrip","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"value","type":"bool"}],"name":"setIsUsingCCTP","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"sourceChain","type":"uint16"},{"internalType":"bytes32","name":"sourceAddress","type":"bytes32"}],"name":"setRegisteredSender","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"tokenBridge","outputs":[{"internalType":"contract ITokenBridge","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"assetAmount","type":"uint256"},{"internalType":"uint256","name":"costForReturnDelivery","type":"uint256"}],"name":"withdrawCollateral","outputs":[{"internalType":"uint64","name":"sequence","type":"uint64"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assetAmount","type":"uint256"},{"internalType":"uint256","name":"costForReturnDelivery","type":"uint256"},{"internalType":"bool","name":"unwrap","type":"bool"}],"name":"withdrawCollateralNative","outputs":[{"internalType":"uint64","name":"sequence","type":"uint64"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"wormhole","outputs":[{"internalType":"contract IWormhole","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"wormholeRelayer","outputs":[{"internalType":"contract IWormholeRelayer","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]