Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"contracts/token/TokenBridgeCctpV2.sol": {
"content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
import {TokenBridgeCctpBase} from "./TokenBridgeCctpBase.sol";
import {TokenRouter} from "./libs/TokenRouter.sol";
import {TypedMemView} from "./../libs/TypedMemView.sol";
import {Message} from "./../libs/Message.sol";
import {TokenMessage} from "./libs/TokenMessage.sol";
import {CctpMessageV2, BurnMessageV2} from "../libs/CctpMessageV2.sol";
import {TypeCasts} from "../libs/TypeCasts.sol";
import {IMessageHandlerV2} from "../interfaces/cctp/IMessageHandlerV2.sol";
import {ITokenMessengerV2} from "../interfaces/cctp/ITokenMessengerV2.sol";
import {IMessageTransmitterV2} from "../interfaces/cctp/IMessageTransmitterV2.sol";
// @dev Supports only CCTP V2
contract TokenBridgeCctpV2 is TokenBridgeCctpBase, IMessageHandlerV2 {
using CctpMessageV2 for bytes29;
using BurnMessageV2 for bytes29;
using TypedMemView for bytes29;
using Message for bytes;
using TypeCasts for bytes32;
// see https://developers.circle.com/cctp/cctp-finality-and-fees#defined-finality-thresholds
uint32 public immutable minFinalityThreshold;
uint256 public immutable maxFeeBps;
constructor(
address _erc20,
address _mailbox,
IMessageTransmitterV2 _messageTransmitter,
ITokenMessengerV2 _tokenMessenger,
uint256 _maxFeeBps,
uint32 _minFinalityThreshold
)
TokenBridgeCctpBase(
_erc20,
_mailbox,
_messageTransmitter,
_tokenMessenger
)
{
require(_maxFeeBps < 10_000, "maxFeeBps must be less than 100%");
maxFeeBps = _maxFeeBps;
minFinalityThreshold = _minFinalityThreshold;
}
// ============ TokenRouter overrides ============
/**
* @inheritdoc TokenRouter
* @dev Overrides to indicate v2 fees.
*
* Hyperlane uses a "minimum amount out" approach where users specify the exact amount
* they want the recipient to receive on the destination chain. This provides a better
* UX by guaranteeing predictable outcomes regardless of underlying bridge fee structures.
*
* However, some underlying bridges like CCTP charge fees as a percentage of the input
* amount (amountIn), not the output amount. This requires "reversing" the fee calculation:
* we need to determine what input amount (after fees are deducted) will result in the
* desired output amount reaching the recipient.
*
* The formula solves for the fee needed such that after Circle takes their percentage,
* the recipient receives exactly `amount`:
*
* (amount + fee) * (10_000 - maxFeeBps) / 10_000 = amount
*
* Solving for fee:
* fee = (amount * maxFeeBps) / (10_000 - maxFeeBps)
*
* Example: If amount = 100 USDC and maxFeeBps = 10 (0.1%):
* fee = (100 * 10) / (10_000 - 10) = 1000 / 9990 ≈ 0.1001 USDC
* We deposit 100.1001 USDC, Circle takes 0.1001 USDC, recipient gets exactly 100 USDC.
*/
function _externalFeeAmount(
uint32,
bytes32,
uint256 amount
) internal view override returns (uint256 feeAmount) {
return (amount * maxFeeBps) / (10_000 - maxFeeBps);
}
function _getCCTPVersion() internal pure override returns (uint32) {
return 1;
}
function _getCircleRecipient(
bytes29 cctpMessage
) internal pure override returns (address) {
return cctpMessage._getRecipient().bytes32ToAddress();
}
function _validateTokenMessage(
bytes calldata hyperlaneMessage,
bytes29 cctpMessage
) internal pure override {
bytes29 burnMessage = cctpMessage._getMessageBody();
burnMessage._validateBurnMessageFormat();
bytes32 circleBurnSender = burnMessage._getMessageSender();
require(
circleBurnSender == hyperlaneMessage.sender(),
"Invalid burn sender"
);
bytes calldata tokenMessage = hyperlaneMessage.body();
require(
TokenMessage.amount(tokenMessage) == burnMessage._getAmount(),
"Invalid mint amount"
);
require(
TokenMessage.recipient(tokenMessage) ==
burnMessage._getMintRecipient(),
"Invalid mint recipient"
);
}
function _validateHookMessage(
bytes calldata hyperlaneMessage,
bytes29 cctpMessage
) internal pure override {
bytes32 circleMessageId = cctpMessage._getMessageBody().index(0, 32);
require(circleMessageId == hyperlaneMessage.id(), "Invalid message id");
}
// @inheritdoc IMessageHandlerV2
function handleReceiveFinalizedMessage(
uint32 sourceDomain,
bytes32 sender,
uint32 /*finalityThresholdExecuted*/,
bytes calldata messageBody
) external override returns (bool) {
return
_receiveMessageId(
sourceDomain,
sender,
abi.decode(messageBody, (bytes32))
);
}
// @inheritdoc IMessageHandlerV2
function handleReceiveUnfinalizedMessage(
uint32 sourceDomain,
bytes32 sender,
uint32 /*finalityThresholdExecuted*/,
bytes calldata messageBody
) external override returns (bool) {
return
_receiveMessageId(
sourceDomain,
sender,
abi.decode(messageBody, (bytes32))
);
}
function _sendMessageIdToIsm(
uint32 destinationDomain,
bytes32 ism,
bytes32 messageId
) internal override {
IMessageTransmitterV2(address(messageTransmitter)).sendMessage(
destinationDomain,
ism,
bytes32(0), // allow anyone to relay
minFinalityThreshold,
abi.encode(messageId)
);
}
function _bridgeViaCircle(
uint32 circleDomain,
bytes32 _recipient,
uint256 _amount,
uint256 _maxFee
) internal override {
ITokenMessengerV2(address(tokenMessenger)).depositForBurn(
_amount,
circleDomain,
_recipient,
address(wrappedToken),
bytes32(0), // allow anyone to relay
_maxFee,
minFinalityThreshold
);
}
}
"
},
"contracts/token/TokenBridgeCctpBase.sol": {
"content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
import {TokenRouter} from "./libs/TokenRouter.sol";
import {HypERC20Collateral} from "./HypERC20Collateral.sol";
import {IMessageTransmitter} from "./../interfaces/cctp/IMessageTransmitter.sol";
import {IInterchainSecurityModule} from "./../interfaces/IInterchainSecurityModule.sol";
import {AbstractCcipReadIsm} from "./../isms/ccip-read/AbstractCcipReadIsm.sol";
import {TypedMemView} from "./../libs/TypedMemView.sol";
import {ITokenMessenger} from "./../interfaces/cctp/ITokenMessenger.sol";
import {Message} from "./../libs/Message.sol";
import {TokenMessage} from "./libs/TokenMessage.sol";
import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol";
import {StandardHookMetadata} from "../hooks/libs/StandardHookMetadata.sol";
import {IMessageHandler} from "../interfaces/cctp/IMessageHandler.sol";
import {TypeCasts} from "../libs/TypeCasts.sol";
import {MovableCollateralRouter, MovableCollateralRouterStorage} from "./libs/MovableCollateralRouter.sol";
import {TokenRouter} from "./libs/TokenRouter.sol";
import {AbstractPostDispatchHook} from "../hooks/libs/AbstractPostDispatchHook.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
interface CctpService {
function getCCTPAttestation(
bytes calldata _message
)
external
view
returns (bytes memory cctpMessage, bytes memory attestation);
}
// need intermediate contract to insert slots between TokenRouter and AbstractCcipReadIsm
abstract contract TokenBridgeCctpBaseStorage is TokenRouter {
/// @dev This is used to enable storage layout backwards compatibility. It should not be read or written to.
MovableCollateralRouterStorage private __MOVABLE_COLLATERAL_GAP;
}
struct Domain {
uint32 hyperlane;
uint32 circle;
}
// see ./CCTP.md for sequence diagrams of the destination chain control flow
abstract contract TokenBridgeCctpBase is
TokenBridgeCctpBaseStorage,
AbstractCcipReadIsm,
AbstractPostDispatchHook
{
using Message for bytes;
using TypeCasts for bytes32;
using SafeERC20 for IERC20;
uint256 private constant _SCALE = 1;
IERC20 public immutable wrappedToken;
// @notice CCTP message transmitter contract
IMessageTransmitter public immutable messageTransmitter;
// @notice CCTP token messenger contract
ITokenMessenger public immutable tokenMessenger;
/// @notice Hyperlane domain => Domain struct.
/// We use a struct to avoid ambiguity with domain 0 being unknown.
mapping(uint32 hypDomain => Domain circleDomain)
internal _hyperlaneDomainMap;
/// @notice Circle domain => Domain struct.
// We use a struct to avoid ambiguity with domain 0 being unknown.
mapping(uint32 circleDomain => Domain hyperlaneDomain)
internal _circleDomainMap;
/// @notice Maps messageId to whether or not the message has been verified
/// by the CCTP message transmitter
mapping(bytes32 messageId => bool verified) public isVerified;
/**
* @notice Emitted when the Hyperlane domain to Circle domain mapping is updated.
* @param hyperlaneDomain The Hyperlane domain.
* @param circleDomain The Circle domain.
*/
event DomainAdded(uint32 indexed hyperlaneDomain, uint32 circleDomain);
constructor(
address _erc20,
address _mailbox,
IMessageTransmitter _messageTransmitter,
ITokenMessenger _tokenMessenger
) TokenRouter(_SCALE, _mailbox) {
require(
_messageTransmitter.version() == _getCCTPVersion(),
"Invalid messageTransmitter CCTP version"
);
messageTransmitter = _messageTransmitter;
require(
_tokenMessenger.messageBodyVersion() == _getCCTPVersion(),
"Invalid TokenMessenger CCTP version"
);
tokenMessenger = _tokenMessenger;
wrappedToken = IERC20(_erc20);
_disableInitializers();
}
/**
* @inheritdoc TokenRouter
*/
function token() public view override returns (address) {
return address(wrappedToken);
}
function initialize(
address _hook,
address _owner,
string[] memory __urls
) external initializer {
// ISM should not be set
_MailboxClient_initialize(_hook, address(0), _owner);
// Setup urls for offchain lookup and do token approval
setUrls(__urls);
wrappedToken.approve(address(tokenMessenger), type(uint256).max);
}
/**
* @inheritdoc TokenRouter
* @dev Overrides to bridge the tokens via Circle.
*/
function transferRemote(
uint32 _destination,
bytes32 _recipient,
uint256 _amount
) public payable override returns (bytes32 messageId) {
// 1. Calculate the fee amounts, charge the sender and distribute to feeRecipient if necessary
(
uint256 externalFee,
uint256 remainingNativeValue
) = _calculateFeesAndCharge(
_destination,
_recipient,
_amount,
msg.value
);
// 2. Prepare the token message with the recipient, amount, and any additional metadata in overrides
uint32 circleDomain = hyperlaneDomainToCircleDomain(_destination);
uint256 burnAmount = _amount + externalFee;
_bridgeViaCircle(circleDomain, _recipient, burnAmount, externalFee);
bytes memory _message = TokenMessage.format(_recipient, burnAmount);
// 3. Emit the SentTransferRemote event and 4. dispatch the message
return
_emitAndDispatch(
_destination,
_recipient,
_amount, // no scaling needed for CCTP
remainingNativeValue,
_message
);
}
function interchainSecurityModule()
external
view
override
returns (IInterchainSecurityModule)
{
return IInterchainSecurityModule(address(this));
}
/**
* @notice Adds a new mapping between a Hyperlane domain and a Circle domain.
* @param _hyperlaneDomain The Hyperlane domain.
* @param _circleDomain The Circle domain.
*/
function addDomain(
uint32 _hyperlaneDomain,
uint32 _circleDomain
) public onlyOwner {
_hyperlaneDomainMap[_hyperlaneDomain] = Domain(
_hyperlaneDomain,
_circleDomain
);
_circleDomainMap[_circleDomain] = Domain(
_hyperlaneDomain,
_circleDomain
);
emit DomainAdded(_hyperlaneDomain, _circleDomain);
}
function addDomains(Domain[] memory domains) external onlyOwner {
for (uint32 i = 0; i < domains.length; i++) {
addDomain(domains[i].hyperlane, domains[i].circle);
}
}
function hyperlaneDomainToCircleDomain(
uint32 _hyperlaneDomain
) public view returns (uint32) {
Domain memory domain = _hyperlaneDomainMap[_hyperlaneDomain];
require(
domain.hyperlane == _hyperlaneDomain,
"Circle domain not configured"
);
return domain.circle;
}
function circleDomainToHyperlaneDomain(
uint32 _circleDomain
) public view returns (uint32) {
Domain memory domain = _circleDomainMap[_circleDomain];
require(
domain.circle == _circleDomain,
"Hyperlane domain not configured"
);
return domain.hyperlane;
}
function _getCCTPVersion() internal pure virtual returns (uint32);
function _getCircleRecipient(
bytes29 cctpMessage
) internal pure virtual returns (address);
function _validateTokenMessage(
bytes calldata hyperlaneMessage,
bytes29 cctpMessage
) internal pure virtual;
function _validateHookMessage(
bytes calldata hyperlaneMessage,
bytes29 cctpMessage
) internal pure virtual;
function _sendMessageIdToIsm(
uint32 destinationDomain,
bytes32 ism,
bytes32 messageId
) internal virtual;
/**
* @dev Verifies that the CCTP message matches the Hyperlane message.
*/
function verify(
bytes calldata _metadata,
bytes calldata _hyperlaneMessage
) external returns (bool) {
// check if hyperlane message has already been verified by CCTP
if (isVerified[_hyperlaneMessage.id()]) {
return true;
}
// decode return type of CctpService.getCCTPAttestation
(bytes memory cctpMessageBytes, bytes memory attestation) = abi.decode(
_metadata,
(bytes, bytes)
);
bytes29 cctpMessage = TypedMemView.ref(cctpMessageBytes, 0);
address circleRecipient = _getCircleRecipient(cctpMessage);
// check if CCTP message is a USDC burn message
if (circleRecipient == address(tokenMessenger)) {
// prevent hyperlane message recipient configured with CCTP ISM
// from verifying and handling token messages
require(
_hyperlaneMessage.recipientAddress() == address(this),
"Invalid token message recipient"
);
_validateTokenMessage(_hyperlaneMessage, cctpMessage);
}
// check if CCTP message is a GMP message to this contract
else if (circleRecipient == address(this)) {
_validateHookMessage(_hyperlaneMessage, cctpMessage);
}
// disallow other CCTP message destinations
else {
revert("Invalid circle recipient");
}
// for GMP messages, this.verifiedMessages[hyperlaneMessage.id()] will be set
// for token messages, hyperlaneMessage.body().amount() tokens will be delivered to hyperlaneMessage.body().recipient()
return messageTransmitter.receiveMessage(cctpMessageBytes, attestation);
}
function _receiveMessageId(
uint32 circleSource,
bytes32 circleSender,
bytes32 messageId
) internal returns (bool) {
require(
msg.sender == address(messageTransmitter),
"Not message transmitter"
);
// ensure that the message was sent from the hook on the origin chain
uint32 origin = circleDomainToHyperlaneDomain(circleSource);
require(
_mustHaveRemoteRouter(origin) == circleSender,
"Unauthorized circle sender"
);
isVerified[messageId] = true;
return true;
}
function _offchainLookupCalldata(
bytes calldata _message
) internal pure override returns (bytes memory) {
return abi.encodeCall(CctpService.getCCTPAttestation, (_message));
}
/// @inheritdoc IPostDispatchHook
function hookType() external pure override returns (uint8) {
return uint8(IPostDispatchHook.HookTypes.CCTP);
}
/// @inheritdoc AbstractPostDispatchHook
function _quoteDispatch(
bytes calldata /*metadata*/,
bytes calldata /*message*/
) internal pure override returns (uint256) {
return 0;
}
/// @inheritdoc AbstractPostDispatchHook
/// @dev Mirrors the logic in AbstractMessageIdAuthHook._postDispatch
// but using Router table instead of hook <> ISM coupling
function _postDispatch(
bytes calldata metadata,
bytes calldata message
) internal override {
bytes32 id = message.id();
require(_isLatestDispatched(id), "Message not dispatched");
uint32 destination = message.destination();
bytes32 ism = _mustHaveRemoteRouter(destination);
uint32 circleDestination = hyperlaneDomainToCircleDomain(destination);
_sendMessageIdToIsm(circleDestination, ism, id);
_refund(metadata, message, address(this).balance);
}
/**
* @inheritdoc TokenRouter
* @dev Overrides to transfer the tokens from the sender to this contract (like HypERC20Collateral).
*/
function _transferFromSender(uint256 _amount) internal override {
wrappedToken.safeTransferFrom(msg.sender, address(this), _amount);
}
/**
* @inheritdoc TokenRouter
* @dev Overrides to not transfer the tokens to the recipient, as the CCTP transfer will do it.
*/
function _transferTo(
address _recipient,
uint256 _amount
) internal override {
// do not transfer to recipient as the CCTP transfer will do it
}
/**
* @inheritdoc TokenRouter
* @dev Overrides to transfer fees directly from the router balance since CCTP handles token delivery.
*/
function _transferFee(
address _recipient,
uint256 _amount
) internal override {
wrappedToken.safeTransfer(_recipient, _amount);
}
function _bridgeViaCircle(
uint32 _destination,
bytes32 _recipient,
uint256 _amount,
uint256 _maxFee
) internal virtual;
}
"
},
"contracts/token/libs/TokenRouter.sol": {
"content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
// ============ Internal Imports ============
import {TypeCasts} from "../../libs/TypeCasts.sol";
import {GasRouter} from "../../client/GasRouter.sol";
import {TokenMessage} from "./TokenMessage.sol";
import {Quote, ITokenBridge, ITokenFee} from "../../interfaces/ITokenBridge.sol";
import {Quotes} from "./Quotes.sol";
import {StorageSlot} from "@openzeppelin/contracts/utils/StorageSlot.sol";
/**
* @title Hyperlane Token Router that extends Router with abstract token (ERC20/ERC721) remote transfer functionality.
* @dev Overridable functions:
* - token(): specify the managed token address
* - _transferFromSender(uint256): pull tokens/ETH from msg.sender
* - _transferTo(address,uint256): send tokens/ETH to the recipient
* - _externalFeeAmount(uint32,bytes32,uint256): compute external fees (default returns 0)
* @dev Override transferRemote only to implement custom logic that can't be accomplished with the above functions.
*
* @author Abacus Works
*/
abstract contract TokenRouter is GasRouter, ITokenBridge {
using TypeCasts for bytes32;
using TypeCasts for address;
using TokenMessage for bytes;
using StorageSlot for bytes32;
using Quotes for Quote[];
/**
* @dev Emitted on `transferRemote` when a transfer message is dispatched.
* @param destination The identifier of the destination chain.
* @param recipient The address of the recipient on the destination chain.
* @param amountOrId The amount or ID of tokens sent in to the remote recipient.
*/
event SentTransferRemote(
uint32 indexed destination,
bytes32 indexed recipient,
uint256 amountOrId
);
/**
* @dev Emitted on `_handle` when a transfer message is processed.
* @param origin The identifier of the origin chain.
* @param recipient The address of the recipient on the destination chain.
* @param amountOrId The amount or ID of tokens received from the remote sender.
*/
event ReceivedTransferRemote(
uint32 indexed origin,
bytes32 indexed recipient,
uint256 amountOrId
);
uint256 public immutable scale;
// cannot use compiler assigned slot without
// breaking backwards compatibility of storage layout
bytes32 private constant FEE_RECIPIENT_SLOT =
keccak256("FungibleTokenRouter.feeRecipient");
event FeeRecipientSet(address feeRecipient);
constructor(uint256 _scale, address _mailbox) GasRouter(_mailbox) {
scale = _scale;
}
// ===========================
// ========== Main API ==========
// ===========================
/**
* @notice Returns the address of the token managed by this router. It can be one of three options:
* - ERC20 token address for fungible tokens that are being collateralized (HypERC20Collateral, HypERC4626, etc.)
* - 0x0 address for native tokens (ETH, MATIC, etc.) (HypNative, etc.)
* - address(this) for synthetic ERC20 tokens (HypERC20, etc.)
* It is being used for quotes and fees from the fee recipient and pulling/push the tokens from the sender/receipient.
* @dev This function must be implemented by derived contracts to specify the token address.
* @return The address of the token contract.
*/
function token() public view virtual returns (address);
/**
* @inheritdoc ITokenFee
* @notice Implements the standardized fee quoting interface for token transfers based on
* overridable internal functions of _quoteGasPayment, _feeRecipientAndAmount, and _externalFeeAmount.
* @param _destination The identifier of the destination chain.
* @param _recipient The address of the recipient on the destination chain.
* @param _amount The amount or identifier of tokens to be sent to the remote recipient
* @return quotes An array of Quote structs representing the fees in different tokens.
* @dev This function may return multiple quotes with the same denomination. Convention is to return:
* [index 0] native fees charged by the mailbox dispatch
* [index 1] then any internal warp route fees (amount bridged plus fee recipient)
* [index 2] then any external bridging fees (if any, else 0)
* These are surfaced as separate elements to enable clients to interpret/render fees independently.
* There is a Quotes library with an extract function for onchain quoting in a specific denomination,
* but we discourage onchain quoting in favor of offchain quoting and overpaying with refunds.
*/
function quoteTransferRemote(
uint32 _destination,
bytes32 _recipient,
uint256 _amount
) external view override returns (Quote[] memory quotes) {
quotes = new Quote[](3);
quotes[0] = Quote({
token: address(0),
amount: _quoteGasPayment(_destination, _recipient, _amount)
});
(, uint256 feeAmount) = _feeRecipientAndAmount(
_destination,
_recipient,
_amount
);
quotes[1] = Quote({token: token(), amount: _amount + feeAmount});
quotes[2] = Quote({
token: token(),
amount: _externalFeeAmount(_destination, _recipient, _amount)
});
}
/**
* @notice Transfers `_amount` token to `_recipient` on the `_destination` domain.
* @dev Delegates transfer logic to `_transferFromSender` implementation.
* Emits `SentTransferRemote` event on the origin chain.
* Override with custom behavior for storing or forwarding tokens.
* Known overrides:
* - OPL2ToL1TokenBridgeNative: adds hook metadata for message dispatch.
* - EverclearTokenBridge: creates Everclear intent for cross-chain token transfer.
* - TokenBridgeCctpBase: adds CCTP-specific metadata for message dispatch.
* - HypERC4626Collateral: deposits into vault and handles shares.
* When overriding, mirror the general flow of this function for consistency:
* 1. Calculate fees and charge the sender.
* 2. Prepare the token message with recipient, amount, and any additional metadata.
* 3. Emit `SentTransferRemote` event.
* 4. Dispatch the message.
* @param _destination The identifier of the destination chain.
* @param _recipient The address of the recipient on the destination chain.
* @param _amount The amount or identifier of tokens to be sent to the remote recipient.
* @return messageId The identifier of the dispatched message.
*/
function transferRemote(
uint32 _destination,
bytes32 _recipient,
uint256 _amount
) public payable virtual returns (bytes32 messageId) {
// 1. Calculate the fee amounts, charge the sender and distribute to feeRecipient if necessary
(, uint256 remainingNativeValue) = _calculateFeesAndCharge(
_destination,
_recipient,
_amount,
msg.value
);
uint256 scaledAmount = _outboundAmount(_amount);
// 2. Prepare the token message with the recipient and amount
bytes memory _tokenMessage = TokenMessage.format(
_recipient,
scaledAmount
);
// 3. Emit the SentTransferRemote event and 4. dispatch the message
return
_emitAndDispatch(
_destination,
_recipient,
scaledAmount,
remainingNativeValue,
_tokenMessage
);
}
// ===========================
// ========== Internal convenience functions for readability ==========
// ==========================
function _calculateFeesAndCharge(
uint32 _destination,
bytes32 _recipient,
uint256 _amount,
uint256 _msgValue
) internal returns (uint256 externalFee, uint256 remainingNativeValue) {
(address _feeRecipient, uint256 feeAmount) = _feeRecipientAndAmount(
_destination,
_recipient,
_amount
);
externalFee = _externalFeeAmount(_destination, _recipient, _amount);
uint256 charge = _amount + feeAmount + externalFee;
_transferFromSender(charge);
if (feeAmount > 0) {
// transfer atomically so we don't need to keep track of collateral
// and fee balances separately
_transferFee(_feeRecipient, feeAmount);
}
remainingNativeValue = token() != address(0)
? _msgValue
: _msgValue - charge;
}
// Emits the SentTransferRemote event and dispatches the message.
function _emitAndDispatch(
uint32 _destination,
bytes32 _recipient,
uint256 _amount,
uint256 _messageDispatchValue,
bytes memory _tokenMessage
) internal returns (bytes32 messageId) {
// effects
emit SentTransferRemote(_destination, _recipient, _amount);
// interactions
messageId = _Router_dispatch(
_destination,
_messageDispatchValue,
_tokenMessage,
_GasRouter_hookMetadata(_destination),
address(hook)
);
}
// ===========================
// ========== Fees & Quoting ==========
// ===========================
/**
* @notice Sets the fee recipient for the router.
* @dev Allows for address(0) to be set, which disables fees.
* @param recipient The address that receives fees.
*/
function setFeeRecipient(address recipient) public onlyOwner {
require(recipient != address(this), "Fee recipient cannot be self");
FEE_RECIPIENT_SLOT.getAddressSlot().value = recipient;
emit FeeRecipientSet(recipient);
}
/**
* @notice Returns the address of the fee recipient.
* @dev Returns address(0) if no fee recipient is set.
* @dev Can be overridden with address(0) to disable fees entirely.
* @return address of the fee recipient.
*/
function feeRecipient() public view virtual returns (address) {
return FEE_RECIPIENT_SLOT.getAddressSlot().value;
}
// To be overridden by derived contracts if they have additional fees
/**
* @notice Returns the external fee amount for the given parameters.
* param _destination The identifier of the destination chain.
* param _recipient The address of the recipient on the destination chain.
* param _amount The amount or identifier of tokens to be sent to the remote recipient
* @return feeAmount The external fee amount.
* @dev This fee must be denominated in the `token()` defined by this router.
* @dev The default implementation returns 0, meaning no external fees are charged.
* This function is intended to be overridden by derived contracts that have additional fees.
* Known overrides:
* - TokenBridgeCctpBase: for CCTP-specific fees
* - EverclearTokenBridge: for Everclear-specific fees
*/
function _externalFeeAmount(
uint32, // _destination,
bytes32, // _recipient,
uint256 // _amount
) internal view virtual returns (uint256 feeAmount) {
return 0;
}
/**
* @notice Returns the fee recipient amount for the given parameters.
* @param _destination The identifier of the destination chain.
* @param _recipient The address of the recipient on the destination chain.
* @param _amount The amount or identifier of tokens to be sent to the remote recipient
* @return _feeRecipient The address of the fee recipient.
* @return feeAmount The fee recipient amount.
* @dev This function is is not intended to be overridden as storage and logic is contained in TokenRouter.
*/
function _feeRecipientAndAmount(
uint32 _destination,
bytes32 _recipient,
uint256 _amount
) internal view returns (address _feeRecipient, uint256 feeAmount) {
_feeRecipient = feeRecipient();
if (_feeRecipient == address(0)) {
return (_feeRecipient, 0);
}
Quote[] memory quotes = ITokenFee(_feeRecipient).quoteTransferRemote(
_destination,
_recipient,
_amount
);
if (quotes.length == 0) {
return (_feeRecipient, 0);
}
require(
quotes.length == 1 && quotes[0].token == token(),
"FungibleTokenRouter: fee must match token"
);
feeAmount = quotes[0].amount;
}
/**
* @notice Returns the gas payment required to dispatch a message to the given domain's router.
* @param _destination The identifier of the destination chain.
* @param _recipient The address of the recipient on the destination chain.
* @param _amount The amount or identifier of tokens to be sent to the remote recipient
* @return payment How much native value to send in transferRemote call.
* @dev This function is intended to be overridden by derived contracts that trigger multiple messages.
* Known overrides:
* - OPL2ToL1TokenBridgeNative: Quote for two messages (prove and finalize).
*/
function _quoteGasPayment(
uint32 _destination,
bytes32 _recipient,
uint256 _amount
) internal view virtual returns (uint256) {
return
_Router_quoteDispatch(
_destination,
TokenMessage.format(_recipient, _amount),
_GasRouter_hookMetadata(_destination),
address(hook)
);
}
// ===========================
// ========== Internal virtual functions for token handling ==========
// ===========================
/**
* @dev Should transfer `_amount` of tokens from `msg.sender` to this token router.
* Called by `transferRemote` before message dispatch.
* Known overrides:
* - HypERC20: Burns the tokens from the sender.
* - HypERC20Collateral: Pulls the tokens from the sender.
* - HypNative: Asserts msg.value >= _amount
* - TokenBridgeCctpBase: (like HypERC20Collateral) Pulls the tokens from the sender.
* - EverclearEthTokenBridge: Wraps the native token (ETH) to WETH
* - HypERC4626: Converts the amounts to shares and burns from the User (via HypERC20 implementation)
* - HypFiatToken: Pulls the tokens from the sender and burns them on the FiatToken contract.
* - HypXERC20: Burns the tokens from the sender.
* - HypXERC20Lockbox: Pulls the tokens from the sender, locks them in the XERC20Lockbox contract and burns the resulting xERC20 tokens.
*/
function _transferFromSender(uint256 _amountOrId) internal virtual;
/**
* @dev Should transfer `_amountOrId` of tokens from this token router to `_recipient`.
* @dev Called by `handle` after message decoding.
* Known overrides:
* - HypERC20: Mints the tokens to the recipient.
* - HypERC20Collateral: Releases the tokens to the recipient.
* - HypNative: Releases native tokens to the recipient.
* - TokenBridgeCctpBase: Do nothing (CCTP transfers tokens to the recipient directly).
* - EverclearEthTokenBridge: Unwraps WETH to ETH and sends to the recipient.
* - HypERC4626: Converts the amount to shares and mints to the User (via HypERC20 implementation)
* - HypFiatToken: Mints the tokens to the recipient on the FiatToken contract.
* - HypXERC20: Mints the tokens to the recipient.
* - HypXERC20Lockbox: Withdraws the underlying tokens from the Lockbox and sends to the recipient.
* - OpL1NativeTokenBridge: Do nothing (the L2 bridge transfers the native tokens to the recipient directly).
*/
function _transferTo(
address _recipient,
uint256 _amountOrId
) internal virtual;
/**
* @dev Should transfer `_amount` of tokens from this token router to the fee recipient.
* @dev Called by `_calculateFeesAndCharge` when fee recipient is set and feeAmount > 0.
* @dev The default implementation delegates to `_transferTo`, which works for most token routers
* where tokens are held by the router (e.g., collateral routers, synthetic token routers).
* @dev Override this function for bridges where tokens are NOT held by the router but fees still
* need to be paid (e.g., CCTP, Everclear). In those cases, use direct token transfers from the
* router's balance collected via `_transferFromSender`.
* Known overrides:
* - TokenBridgeCctpBase: Directly transfers tokens from router balance.
* - EverclearTokenBridge: Directly transfers tokens from router balance.
*/
function _transferFee(
address _recipient,
uint256 _amount
) internal virtual {
_transferTo(_recipient, _amount);
}
/**
* @dev Scales local amount to message amount (up by scale factor).
* Known overrides:
* - HypERC4626: Scales by exchange rate
*/
function _outboundAmount(
uint256 _localAmount
) internal view virtual returns (uint256 _messageAmount) {
_messageAmount = _localAmount * scale;
}
/**
* @dev Scales message amount to local amount (down by scale factor).
* Known overrides:
* - HypERC4626: Scales by exchange rate
*/
function _inboundAmount(
uint256 _messageAmount
) internal view virtual returns (uint256 _localAmount) {
_localAmount = _messageAmount / scale;
}
/**
* @notice Handles the incoming transfer message.
* It decodes the message, emits the ReceivedTransferRemote event, and transfers tokens to the recipient.
* @param _origin The identifier of the origin chain.
* @dev param _sender The address of the sender router on the origin chain.
* @param _message The message data containing recipient and amount.
* @dev Override this function if custom logic is required for sending out the tokens.
* Known overrides:
* - EverclearTokenBridge: Receives the tokens and sends them to the recipient.
* - EverclearEthBridge: Receives WETH, unwraps it and sends native ETH to the recipient.
* - HypERC4626: Updates the exchange rate from the metadata
*/
// solhint-disable-next-line hyperlane/no-virtual-override
function _handle(
uint32 _origin,
bytes32,
bytes calldata _message
) internal virtual override {
bytes32 recipient = _message.recipient();
uint256 amount = _message.amount();
// effects
emit ReceivedTransferRemote(_origin, recipient, amount);
// interactions
_transferTo(recipient.bytes32ToAddress(), _inboundAmount(amount));
}
}
"
},
"contracts/libs/TypedMemView.sol": {
"content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.12;
library TypedMemView {
// Why does this exist?
// the solidity `bytes memory` type has a few weaknesses.
// 1. You can't index ranges effectively
// 2. You can't slice without copying
// 3. The underlying data may represent any type
// 4. Solidity never deallocates memory, and memory costs grow
// superlinearly
// By using a memory view instead of a `bytes memory` we get the following
// advantages:
// 1. Slices are done on the stack, by manipulating the pointer
// 2. We can index arbitrary ranges and quickly convert them to stack types
// 3. We can insert type info into the pointer, and typecheck at runtime
// This makes `TypedMemView` a useful tool for efficient zero-copy
// algorithms.
// Why bytes29?
// We want to avoid confusion between views, digests, and other common
// types so we chose a large and uncommonly used odd number of bytes
//
// Note that while bytes are left-aligned in a word, integers and addresses
// are right-aligned. This means when working in assembly we have to
// account for the 3 unused bytes on the righthand side
//
// First 5 bytes are a type flag.
// - ff_ffff_fffe is reserved for unknown type.
// - ff_ffff_ffff is reserved for invalid types/errors.
// next 12 are memory address
// next 12 are len
// bottom 3 bytes are empty
// Assumptions:
// - non-modification of memory.
// - No Solidity updates
// - - wrt free mem point
// - - wrt bytes representation in memory
// - - wrt memory addressing in general
// Usage:
// - create type constants
// - use `assertType` for runtime type assertions
// - - unfortunately we can't do this at compile time yet :(
// - recommended: implement modifiers that perform type checking
// - - e.g.
// - - `uint40 constant MY_TYPE = 3;`
// - - ` modifier onlyMyType(bytes29 myView) { myView.assertType(MY_TYPE); }`
// - instantiate a typed view from a bytearray using `ref`
// - use `index` to inspect the contents of the view
// - use `slice` to create smaller views into the same memory
// - - `slice` can increase the offset
// - - `slice can decrease the length`
// - - must specify the output type of `slice`
// - - `slice` will return a null view if you try to overrun
// - - make sure to explicitly check for this with `notNull` or `assertType`
// - use `equal` for typed comparisons.
// The null view
bytes29 public constant NULL =
hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
// Mask a low uint96
uint256 constant LOW_12_MASK = 0xffffffffffffffffffffffff;
// Shift constants
uint8 constant SHIFT_TO_LEN = 24;
uint8 constant SHIFT_TO_LOC = 96 + 24;
uint8 constant SHIFT_TO_TYPE = 96 + 96 + 24;
// For nibble encoding
bytes private constant NIBBLE_LOOKUP = "0123456789abcdef";
/**
* @notice Returns the encoded hex character that represents the lower 4 bits of the argument.
* @param _byte The byte
* @return _char The encoded hex character
*/
function nibbleHex(uint8 _byte) internal pure returns (uint8 _char) {
uint8 _nibble = _byte & 0x0f; // keep bottom 4, 0 top 4
_char = uint8(NIBBLE_LOOKUP[_nibble]);
}
/**
* @notice Returns a uint16 containing the hex-encoded byte.
* @param _b The byte
* @return encoded - The hex-encoded byte
*/
function byteHex(uint8 _b) internal pure returns (uint16 encoded) {
encoded |= nibbleHex(_b >> 4); // top 4 bits
encoded <<= 8;
encoded |= nibbleHex(_b); // lower 4 bits
}
/**
* @notice Encodes the uint256 to hex. `first` contains the encoded top 16 bytes.
* `second` contains the encoded lower 16 bytes.
*
* @param _b The 32 bytes as uint256
* @return first - The top 16 bytes
* @return second - The bottom 16 bytes
*/
function encodeHex(
uint256 _b
) internal pure returns (uint256 first, uint256 second) {
for (uint8 i = 31; i > 15; ) {
uint8 _byte = uint8(_b >> (i * 8));
first |= byteHex(_byte);
if (i != 16) {
first <<= 16;
}
unchecked {
i -= 1;
}
}
// abusing underflow here =_=
for (uint8 i = 15; i < 255; ) {
uint8 _byte = uint8(_b >> (i * 8));
second |= byteHex(_byte);
if (i != 0) {
second <<= 16;
}
unchecked {
i -= 1;
}
}
}
/**
* @notice Changes the endianness of a uint256.
* @dev https://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel
* @param _b The unsigned integer to reverse
* @return v - The reversed value
*/
function reverseUint256(uint256 _b) internal pure returns (uint256 v) {
v = _b;
// swap bytes
v =
((v >> 8) &
0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) |
((v &
0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) <<
8);
// swap 2-byte long pairs
v =
((v >> 16) &
0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) |
((v &
0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) <<
16);
// swap 4-byte long pairs
v =
((v >> 32) &
0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) |
((v &
0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) <<
32);
// swap 8-byte long pairs
v =
((v >> 64) &
0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) |
((v &
0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) <<
64);
// swap 16-byte long pairs
v = (v >> 128) | (v << 128);
}
/**
* @notice Create a mask with the highest `_len` bits set.
* @param _len The length
* @return mask - The mask
*/
function leftMask(uint8 _len) private pure returns (uint256 mask) {
// ugly. redo without assembly?
assembly {
// solhint-disable-previous-line no-inline-assembly
mask := sar(
sub(_len, 1),
0x8000000000000000000000000000000000000000000000000000000000000000
)
}
}
/**
* @notice Return the null view.
* @return bytes29 - The null view
*/
function nullView() internal pure returns (bytes29) {
return NULL;
}
/**
* @notice Check if the view is null.
* @return bool - True if the view is null
*/
function isNull(bytes29 memView) internal pure returns (bool) {
return memView == NULL;
}
/**
* @notice Check if the view is not null.
* @return bool - True if the view is not null
*/
function notNull(bytes29 memView) internal pure returns (bool) {
return !isNull(memView);
}
/**
* @notice Check if the view is of a valid type and points to a valid location
* in memory.
* @dev We perform this check by examining solidity's unallocated memory
* pointer and ensuring that the view's upper bound is less than that.
* @param memView The view
* @return ret - True if the view is valid
*/
function isValid(bytes29 memView) internal pure returns (bool ret) {
if (typeOf(memView) == 0xffffffffff) return false;
uint256 _end = end(memView);
assembly {
// solhint-disable-previous-line no-inline-assembly
ret := iszero(gt(_end, mload(0x40)))
}
}
/**
* @notice Require that a typed memory view be valid.
* @dev Returns the view for easy chaining.
* @param memView The view
* @return bytes29 - The validated view
*/
function assertValid(bytes29 memView) internal pure returns (bytes29) {
require(isValid(memView), "Validity assertion failed");
return memView;
}
/**
* @notice Return true if the memview is of the expected type. Otherwise false.
* @param memView The view
* @param _expected The expected type
* @return bool - True if the memview is of the expected type
*/
function isType(
bytes29 memView,
uint40 _expected
) internal pure returns (bool) {
return typeOf(memView) == _expected;
}
/**
* @notice Require that a typed memory view has a specific type.
* @dev Returns the view for easy chaining.
* @param memView The view
* @param _expected The expected type
* @return bytes29 - The view with validated type
*/
function assertType(
bytes29 memView,
uint40 _expected
) internal pure returns (bytes29) {
if (!isType(memView, _expected)) {
(, uint256 g) = encodeHex(uint256(typeOf(memView)));
(, uint256 e) = encodeHex(uint256(_expected));
string memory err = string(
abi.encodePacked(
"Type assertion failed. Got 0x",
uint80(g),
". Expected 0x",
uint80(e)
)
);
revert(err);
}
return memView;
}
/**
* @notice Return an identical view with a different type.
* @param memView The view
* @param _newType The new type
* @return newView - The new view with the specified type
*/
function castTo(
bytes29 memView,
uint40 _newType
) internal pure returns (bytes29 newView) {
// then | in the new type
uint256 _typeShift = SHIFT_TO_TYPE;
uint256 _typeBits = 40;
assembly {
// solhint-disable-previous-line no-inline-assembly
// shift off the top 5 bytes
newView := or(newView, shr(_typeBits, shl(_typeBits, memView)))
newView := or(newView, shl(_typeShift, _newType))
}
}
/**
* @notice Unsafe raw pointer construction. This should generally not be called
* directly. Prefer `ref` wherever possible.
* @dev Unsafe raw pointer construction. This should generally not be called
* directly. Prefer `ref` wherever possible.
* @param _type The type
* @param _loc The memory address
* @param _len The length
* @return newView - The new view with the specified type, location and length
*/
function unsafeBuildUnchecked(
uint256 _type,
uint256 _loc,
uint256 _len
) private pure returns (bytes29 newView) {
uint256 _uint96Bits = 96;
uint256 _emptyBits = 24;
assembly {
// solium-disable-previous-line security/no-inline-assembly
newView := shl(_uint96Bits, or(newView, _type)) // insert type
newView := shl(_uint96Bits, or(newView, _loc)) // insert loc
newView := shl(_emptyBits, or(newView, _len)) // empty bottom 3 bytes
}
}
/**
* @notice Instantiate a new memory view. This should generally not be called
* directly. Prefer `ref` wherever possible.
* @dev Instantiate a new memory view. This should generally not be called
* directly. Prefer `ref` wherever possible.
* @param _type The type
* @param _loc The memory address
* @param _len The length
* @return newView - The new view with the specified type, location and length
*/
function build(
uint256 _type,
uint256 _loc,
uint256 _len
) internal pure returns (bytes29 newView) {
uint256 _end = _loc + _len;
assembly {
// solhint-disable-previous-line no-inline-assembly
if gt(_end, mload(0x40)) {
_end := 0
}
}
if (_end == 0) {
return NULL;
}
newView = unsafeBuildUnchecked(_type, _loc, _len);
}
/**
* @notice Instantiate a memory view from a byte array.
* @dev Note that due to Solidity memory representation, it is not possible to
* implement a deref, as the `bytes` type stores its len in memory.
* @param arr The byte array
* @param newType The type
* @return bytes29 - The memory view
*/
function ref(
bytes memory arr,
uint40 newType
) internal pure returns (bytes29) {
uint256 _len = arr.length;
uint256 _loc;
assembly {
// solhint-disable-previous-line no-inline-assembly
_loc := add(arr, 0x20) // our view is of the data, not the struct
}
return build(newType, _loc, _len);
}
/**
* @notice Return the associated type information.
* @param memView The memory view
* @return _type - The type associated with the view
*/
function typeOf(bytes29 memView) internal pure returns (uint40 _type) {
uint256 _shift = SHIFT_TO_TYPE;
assembly {
// solium-disable-previous-line security/no-inline-assembly
_type := shr(_shift, memView) // shift out lower 27 bytes
}
}
/**
* @notice Optimized type comparison. Checks that the 5-byte type flag is equal.
* @param left The first view
* @param right The second view
* @return bool - True if the 5-byte type flag is equal
*/
function sameType(
bytes29 left,
bytes29 right
) internal pure returns (bool) {
return (left ^ right) >> SHIFT_TO_TYPE == 0;
}
/**
* @notice Return the memory address of the underlying bytes.
* @param memView The view
* @return _loc - The memory address
*/
function loc(bytes29 memView) internal pure returns (uint96 _loc) {
uint256 _mask = LOW_12_MASK; // assembly can't use globals
uint256 _shift = SHIFT_TO_LOC;
assembly {
// solium-disable-previous-line security/no-inline-assembly
_loc := and(shr(_shift, memView), _mask)
}
}
/**
* @notice The number of memory words this memory view occupies, rounded up.
* @param memView The view
* @return uint256 - The number of memory words
*/
function words(bytes29 memView) internal pure returns (uint256) {
return (uint256(len(memView)) + 31) / 32;
}
/**
* @notice The in-memory footprint of a fresh copy of the view.
* @param memView The view
* @return uint256 - The in-memory footprint of a fresh copy of the view.
*/
function footprint(bytes29 memView) internal pure returns (uint256) {
return words(memView) * 32;
}
/**
* @notice The number of bytes of the view.
* @param memView The view
* @return _len - The length of the view
*/
function len(bytes29 memView) internal pure returns (uint96 _len) {
uint256 _mask = LOW_12_MASK; // assembly can't use globals
uint256 _emptyBits = 24;
assembly {
// solium-disable-previous-line security/no-inline-assembly
_len := and(shr(_emptyBits, memView), _mask)
}
}
/**
* @notice Returns the endpoint of `memView`.
* @param memView The view
* @return uint256 - The endpoint of `memView`
*/
function end(bytes29 memView) internal pure returns (uint256) {
unchecked {
return loc(memView) + len(memView);
}
}
/**
* @notice Safe slicing without memory modification.
* @param memView The view
* @param _index The start index
* @param _len The length
* @param newType The new type
* @return bytes29 - The new view
*/
function slice(
bytes29 memView,
uint256 _index,
uint256 _len,
uint40 newType
) internal pure returns (bytes29) {
uint256 _loc = loc(memView);
// Ensure it doesn't overrun the view
if (_loc + _index + _len > end(memView)) {
return NULL;
}
_loc = _loc + _index;
return build(newType, _loc, _len);
}
/**
* @notice Shortcut to `slice`. Gets a view representing the first `_len` bytes.
* @param memView The view
* @param _len The length
* @param newType The new type
* @return bytes29 - The new view
*/
function prefix(
bytes29 memView,
uint256 _len,
uint40 newType
) internal pure returns (bytes29) {
return slice(memView, 0, _len, newType);
}
/**
* @notice Shortcut to `slice`. Gets a view representing the last `_len` byte.
* @param memView The view
* @param _len The length
* @param newType The new type
* @return bytes29 - The new view
*/
function postfix(
bytes29 memView,
uint256 _len,
uint40 newType
) internal pure returns (bytes29) {
return slice(memView, uint256(len(memView)) - _len, _len, newType);
}
/**
* @notice Construct an error message for an indexing overrun.
* @param _loc The memory address
* @param _len The length
* @param _index The index
* @param _slice The slice where the overrun occurred
* @return err - The err
*/
function indexErrOverrun(
uint256 _loc,
uint256 _len,
uint256 _index,
uint256 _slice
) internal pure returns (string memory err) {
(, uint256 a) = encodeHex(_loc);
(, uint256 b) = encodeHex(_len);
(, uint256 c) = encodeHex(_index);
(, uint256 d) = encodeHex(_slice);
err = string(
abi.encodePacked(
"TypedMemView/index - Overran the view. Slice is at 0x",
uint48(a),
" with length 0x",
uint48(b),
". Attempted to index at offset 0x",
uint48(c),
" with length 0x",
uint48(d),
"."
)
);
}
/**
* @notice Load up to 32 bytes from the view onto the stack.
* @dev Returns a bytes32 with only the `_bytes` highest bytes set.
* This can be immediately cast to a smaller fixed-length byte array.
* To automatically cast to an integer, use `indexUint`.
* @param memView The view
* @param _index The index
* @param _bytes The bytes
* @return result - The 32 byte result
*/
function index(
bytes29 memView,
uint256 _index,
uint8 _bytes
) internal pure returns (bytes32 result) {
if (_bytes == 0) return bytes32(0);
if (_index + _bytes > len(memView)) {
revert(
indexErrOverrun(
loc(memView),
len(memView),
_index,
uint256(_bytes)
)
);
}
require(
_bytes <= 32,
"TypedMemView/index - Attempted to index more than 32 bytes"
);
uint8 bitLength;
unchecked {
bitLength = _bytes * 8;
}
uint256 _loc = loc(memView);
uint256 _mask = leftMask(bitLength);
assembly {
// solhint-disable-previous-line no-inline-assembly
result := and(mload(add(_loc, _index)), _mask)
}
}
/**
* @notice Parse an unsigned integer from the view at `_index`.
* @dev Requires that the view have >= `_bytes` bytes following that index.
* @param memView The view
* @param _index The index
* @param _bytes The bytes
* @return result - The unsigned integer
*/
function indexUint(
bytes29 memView,
uint256 _index,
uint8 _bytes
) internal pure returns (uint256 result) {
return uint256(index(memView, _index, _bytes)) >> ((32 - _bytes) * 8);
}
/**
* @notice Parse an unsigned integer from LE bytes.
* @param memView The view
* @param _index The index
* @param _bytes The bytes
* @return result - The unsigned integer
*/
function indexLEUint(
bytes29 memView,
uint256 _index,
uint8 _bytes
) internal pure returns (uint256 result) {
return reverseUint256(uint256(index(memView, _index, _bytes)));
}
/**
* @notice Parse an address from the view at `_index`. Requires that the view have >= 20 bytes
* following that index.
* @param memView The view
* @param _index The index
* @return address - The address
*/
function indexAddress(
bytes29 memView,
uint256 _index
) internal pure returns (address) {
return address(uint160(indexUint(memView, _index, 20)));
}
/**
* @notice Return the keccak256 hash of the underlying memory
* @param memView The view
* @return digest - The keccak256 hash of the underlying memory
*/
function keccak(bytes29 memView) internal pure returns (bytes32 digest) {
uint256 _loc = loc(memView);
uint256 _len = len(memView);
assembly {
// solhint-disable-previous-line no-inline-assembly
digest := keccak256(_loc, _len)
}
}
/**
* @notice Return the sha2 digest of the underlying memory.
* @dev We explicitly deallocate memory afterwards.
* @param memView The view
* @return digest - The sha2 hash of the underlying memory
*/
function sha2(bytes29 memView) internal view returns (bytes32 digest) {
uint256 _loc = loc(memView);
uint256 _len = len(memView);
bool res;
assembly {
// solhint-disable-previous-line no-inline-assembly
let ptr := mload(0x40)
res := staticcall(gas(), 2, _loc, _len, ptr, 0x20) // sha2 #1
digest := mload(ptr)
}
require(res, "sha2 OOG");
}
/**
* @notice Implements bitcoin's hash160 (rmd160(sha2()))
* @param memView The pre-image
* @return digest - the Digest
*/
function hash160(bytes29 memView) internal view returns (bytes20 digest) {
uint256 _loc = loc(memView);
uint256 _len = len(memView);
bool res;
assembly {
// solhint-disable-previous-line no-inline-assembly
let ptr := mload(0x40)
res := staticcall(gas(), 2, _loc, _len, ptr, 0x20) // sha2
res := and(res, staticcall(gas(), 3, ptr, 0x20, ptr, 0x20)) // rmd160
digest := mload(add(ptr, 0xc)) // return value is 0-prefixed.
}
require(res, "hash160 OOG");
}
/**
* @notice Implements bitcoin's hash256 (double sha2)
* @param memView A view of the preimage
* @return digest - the Digest
*/
function hash256(bytes29 memView) internal view returns (bytes32 digest) {
uint256 _loc = loc(memView);
uint256 _len = len(memView);
bool res;
assembly {
// solhint-disable-previous-line no-inline-assembly
let ptr := mload(0x40)
res := staticcall(gas(), 2, _loc, _len, ptr, 0x20) // sha2 #1
res := and(res, staticcall(gas(), 2, ptr, 0x20, ptr, 0x20)) // sha2 #2
digest := mload(ptr)
}
require(res, "hash256 OOG");
}
/**
* @notice Return true if the underlying memory is equal. Else false.
* @param left The first view
* @param right The second view
* @return bool - True if the underlying memory is equal
*/
function untypedEqual(
bytes29 left,
bytes29 right
) internal pure returns (bool) {
return
(loc(left) == loc(right) && len(left) == len(right)) ||
keccak(left) ==
Submitted on: 2025-11-05 10:45:17
Comments
Log in to comment.
No comments yet.