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": {
"src/GmpManager/GmpManager.sol": {
"content": "// SPDX-License-Identifier: Apache 2
pragma solidity >=0.8.8 <0.9.0;
import "../interfaces/IGmpManager.sol";
import "../libraries/GmpStructs.sol";
import "./GmpIntegration.sol";
import {ManagerBase} from "../NttManager/ManagerBase.sol";
import "wormhole-solidity-sdk/Utils.sol";
contract GmpManager is IGmpManager, ManagerBase {
string public constant GMP_MANAGER_VERSION = "1.0.0";
constructor(
uint16 _chainId
) ManagerBase(_chainId) {}
struct GmpPeer {
bytes32 peerAddress;
}
function __GmpManager_init() internal onlyInitializing {
// check if the owner is the deployer of this contract
if (msg.sender != deployer) {
revert UnexpectedDeployer(deployer, msg.sender);
}
if (msg.value != 0) {
revert UnexpectedMsgValue();
}
__PausedOwnable_init(msg.sender, msg.sender);
__ReentrancyGuard_init();
// NOTE: we bump the message counter to start from 1
// this is so we can use '0' as a sentinel value for unreserved sequences
_useMessageSequence();
}
function _initialize() internal virtual override {
super._initialize();
__GmpManager_init();
// Note: _checkThresholdInvariants() removed since we don't maintain global thresholds
_checkTransceiversInvariants();
}
// =============== Storage ==============================================================
bytes32 private constant PEERS_SLOT = bytes32(uint256(keccak256("gmp.peers")) - 1);
bytes32 private constant RESERVED_SEQUENCES_SLOT =
bytes32(uint256(keccak256("gmp.reservedSequences")) - 1);
// =============== Storage Getters/Setters ==============================================
function _getPeersStorage() internal pure returns (mapping(uint16 => GmpPeer) storage $) {
uint256 slot = uint256(PEERS_SLOT);
assembly ("memory-safe") {
$.slot := slot
}
}
function _getReservedSequencesStorage()
internal
pure
returns (mapping(uint64 => address) storage $)
{
uint256 slot = uint256(RESERVED_SEQUENCES_SLOT);
assembly ("memory-safe") {
$.slot := slot
}
}
// =============== Public Getters ========================================================
function getPeer(
uint16 chainId_
) external view returns (GmpPeer memory) {
return _getPeersStorage()[chainId_];
}
function _verifyPeer(uint16 sourceChainId, bytes32 peerAddress) internal view override {
if (sourceChainId == 0) {
revert InvalidPeerChainIdZero();
}
if (peerAddress == bytes32(0)) {
revert InvalidPeerZeroAddress();
}
if (_getPeersStorage()[sourceChainId].peerAddress != peerAddress) {
revert InvalidPeer(sourceChainId, peerAddress);
}
}
// =============== External Interface =========================================================
/**
* @notice Reserve a message sequence number for later use
* @dev This function allows users to reserve sequence numbers ahead of time,
* which can be useful for applications that need deterministic sequence numbers
* or want to guarantee a specific ordering of messages.
*
* @dev Sequence numbers start at 1 (not 0) to allow 0 to be used as a sentinel value
* to indicate "no reserved sequence" in sendMessage calls.
*
* @dev Reserved sequences are per-sender, meaning only the address that reserved
* a sequence can use it in a subsequent sendMessage call.
*
* @dev Reserved sequences are single-use - once consumed in a sendMessage call,
* they cannot be reused.
*
* @return sequence The reserved sequence number
*/
function reserveMessageSequence() external override returns (uint64 sequence) {
sequence = _useMessageSequence();
_getReservedSequencesStorage()[sequence] = msg.sender;
}
function _verifyAndConsumeReservedMessageSequence(
uint64 sequence
) internal {
if (_getReservedSequencesStorage()[sequence] != msg.sender) {
revert SequenceNotReservedBySender(sequence, msg.sender);
}
_getReservedSequencesStorage()[sequence] = address(0);
}
// this exists just to minimise the number of local variable assignments :(
struct PreparedTransfer {
address[] enabledTransceivers;
TransceiverStructs.TransceiverInstruction[] instructions;
uint256[] priceQuotes;
uint256 totalPriceQuote;
}
/**
* @notice Send a cross-chain message to a target contract
* @dev This function supports both immediate sequence allocation and pre-reserved sequences.
*
* ## Sequence Reservation Flow:
*
* ### Option 1: Immediate Sequence Allocation
* - Pass `reservedSequence = 0` to allocate a new sequence immediately
* - The function will automatically assign the next available sequence number
* - This is the default behavior for most use cases
*
* ### Option 2: Pre-Reserved Sequence Usage
* - First call `reserveMessageSequence()` to obtain a reserved sequence number
* - Then pass that sequence number as `reservedSequence` parameter
* - The function will validate that the sequence was reserved by the caller
* - Once used, the reserved sequence is consumed and cannot be reused
*
* @param targetChain The Wormhole chain ID of the target chain
* @param callee The address of the contract to call on the target chain (32-byte format)
* @param refundAddress The address to refund excess fees to on the target chain (32-byte format)
* @param reservedSequence The pre-reserved sequence to use, or 0 for immediate allocation
* @param data The calldata to execute on the target chain
* @param transceiverInstructions Instructions for transceivers (e.g., gas limits, relayer settings)
*
* @return actualSequence The sequence number assigned to this message
*
*/
function sendMessage(
uint16 targetChain,
bytes32 callee,
bytes32 refundAddress,
uint64 reservedSequence,
bytes calldata data,
bytes calldata transceiverInstructions
) external payable override nonReentrant whenNotPaused returns (uint64 actualSequence) {
return _sendMessage(
targetChain, callee, refundAddress, reservedSequence, data, transceiverInstructions
);
}
function _sendMessage(
uint16 targetChain,
bytes32 callee,
bytes32 refundAddress,
uint64 reservedSequence,
bytes calldata data,
bytes calldata transceiverInstructions
) internal returns (uint64 sequence) {
if (callee == bytes32(0)) {
revert InvalidCallee();
}
if (refundAddress == bytes32(0)) {
revert InvalidRefundAddress();
}
// Handle sequence allocation/reservation
if (reservedSequence == 0) {
// No sequence provided, allocate a new one
sequence = _useMessageSequence();
} else {
_verifyAndConsumeReservedMessageSequence(reservedSequence);
sequence = reservedSequence;
}
bytes memory encodedGmpManagerPayload;
{
GmpStructs.GenericMessage memory message = GmpStructs.GenericMessage({
toChain: targetChain,
callee: callee,
sender: toWormholeFormat(msg.sender),
data: data
});
encodedGmpManagerPayload = TransceiverStructs.encodeNttManagerMessage(
TransceiverStructs.NttManagerMessage(
bytes32(uint256(sequence)),
toWormholeFormat(msg.sender),
GmpStructs.encodeGenericMessage(message)
)
);
}
PreparedTransfer memory preparedTransfer;
{
(
address[] memory enabledTransceivers,
TransceiverStructs.TransceiverInstruction[] memory instructions,
uint256[] memory priceQuotes,
uint256 totalPriceQuote
) = _prepareForTransfer(targetChain, transceiverInstructions);
preparedTransfer = PreparedTransfer({
enabledTransceivers: enabledTransceivers,
instructions: instructions,
priceQuotes: priceQuotes,
totalPriceQuote: totalPriceQuote
});
}
bytes32 peerAddress = _getPeersStorage()[targetChain].peerAddress;
if (peerAddress == bytes32(0)) {
revert InvalidPeer(targetChain, peerAddress);
}
_sendMessageToTransceivers(
targetChain,
refundAddress,
peerAddress,
preparedTransfer.priceQuotes,
preparedTransfer.instructions,
preparedTransfer.enabledTransceivers,
encodedGmpManagerPayload
);
emit MessageSent(
sequence, msg.sender, targetChain, callee, data, preparedTransfer.totalPriceQuote
);
}
function executeMsg(
uint16 sourceChainId,
bytes32 sourceGmpManagerAddress,
TransceiverStructs.NttManagerMessage memory message
) public override nonReentrant whenNotPaused {
(bytes32 digest, bool alreadyExecuted) =
_isMessageExecuted(sourceChainId, sourceGmpManagerAddress, message);
if (alreadyExecuted) {
return;
}
GmpStructs.GenericMessage memory gmp = GmpStructs.parseGenericMessage(message.payload);
if (gmp.toChain != chainId) {
revert InvalidTargetChain(gmp.toChain, chainId);
}
address callee = fromWormholeFormat(gmp.callee);
GmpIntegration(callee).receiveMessage(digest, sourceChainId, gmp.sender, gmp.data);
emit MessageExecuted(digest, sourceChainId, message.sender, callee, gmp.data);
}
// =============== Admin ==============================================================
function setPeer(uint16 peerChainId, bytes32 peerAddress) public onlyOwner {
if (peerChainId == 0) {
revert InvalidPeerChainIdZero();
}
if (peerAddress == bytes32(0)) {
revert InvalidPeerZeroAddress();
}
if (peerChainId == chainId) {
revert InvalidPeerSameChainId();
}
GmpPeer memory oldPeer = _getPeersStorage()[peerChainId];
_getPeersStorage()[peerChainId].peerAddress = peerAddress;
_addToKnownChains(peerChainId);
emit PeerUpdated(peerChainId, oldPeer.peerAddress, peerAddress);
}
}
"
},
"src/interfaces/IGmpManager.sol": {
"content": "// SPDX-License-Identifier: Apache 2
pragma solidity >=0.8.8 <0.9.0;
import "./IManagerBase.sol";
interface IGmpManager is IManagerBase {
/// @notice The caller is not the deployer.
error UnexpectedDeployer(address expectedOwner, address owner);
/// @notice An unexpected msg.value was passed with the call
/// @dev Selector 0xbd28e889.
error UnexpectedMsgValue();
error InvalidTargetChain(uint16 targetChain, uint16 chainId);
error CallFailed(bytes returnData);
error InvalidCallee();
error SequenceNotReserved(uint64 sequence);
error SequenceNotReservedBySender(uint64 sequence, address sender);
/// @notice Emitted when a message is sent to another chain
/// @param sequence The sequence number of the message
/// @param sender The address of the message sender
/// @param targetChain The chain ID of the target chain
/// @param callee The address of the contract to call on the target chain
/// @param data The calldata to be executed on the target chain
/// @param fee The total fee paid for sending the message
event MessageSent(
uint64 indexed sequence,
address indexed sender,
uint16 targetChain,
bytes32 callee,
bytes data,
uint256 fee
);
/// @notice Emitted when a message is executed on this chain
/// @param messageHash The hash of the executed message
/// @param sourceChain The chain ID of the source chain
/// @param sender The address of the message sender on the source chain
/// @param callee The address of the contract called on this chain
/// @param data The calldata executed on this chain
event MessageExecuted(
bytes32 indexed messageHash,
uint16 indexed sourceChain,
bytes32 indexed sender,
address callee,
bytes data
);
/// @notice Emitted when a peer is updated
/// @param chainId The chain ID of the updated peer
/// @param oldPeerAddress The previous address of the peer
/// @param newPeerAddress The new address of the peer
event PeerUpdated(uint16 indexed chainId, bytes32 oldPeerAddress, bytes32 newPeerAddress);
/**
* @notice Reserve a message sequence number for later use
* @dev Sequence numbers start at 1 to allow 0 as sentinel value for "no reserved sequence"
* @dev Reserved sequences are per-sender and single-use
* @return sequence The reserved sequence number
*/
function reserveMessageSequence() external returns (uint64 sequence);
/**
* @notice Send a cross-chain message with optional sequence reservation
* @dev Pass reservedSequence = 0 for immediate allocation, or a pre-reserved sequence number
* @param targetChain The Wormhole chain ID of the target chain
* @param callee The target contract address (32-byte format)
* @param refundAddress The refund address (32-byte format)
* @param reservedSequence Pre-reserved sequence (0 for immediate allocation)
* @param data The calldata to execute on target chain
* @param transceiverInstructions Instructions for transceivers
* @return sequence The sequence number assigned to this message
*/
function sendMessage(
uint16 targetChain,
bytes32 callee,
bytes32 refundAddress,
uint64 reservedSequence,
bytes calldata data,
bytes calldata transceiverInstructions
) external payable returns (uint64 sequence);
}
"
},
"src/libraries/GmpStructs.sol": {
"content": "// SPDX-License-Identifier: Apache 2
pragma solidity >=0.8.8 <0.9.0;
import "wormhole-solidity-sdk/libraries/BytesParsing.sol";
library GmpStructs {
using BytesParsing for bytes;
error PayloadTooLong(uint256 length);
error InvalidPrefix();
/// @dev Prefix for all GenericMesage payloads
/// This is 0x99'G''M''P'
bytes4 constant GMP_PREFIX = 0x99474D50;
struct GenericMessage {
/// @notice target chain
uint16 toChain;
/// @notice contract to deliver the payload to
bytes32 callee;
/// @notice sender of the message
bytes32 sender;
/// @notice calldata to pass to the recipient contract
bytes data;
}
function encodeGenericMessage(
GenericMessage memory message
) internal pure returns (bytes memory) {
if (message.data.length > type(uint16).max) {
revert PayloadTooLong(message.data.length);
}
return abi.encodePacked(
GMP_PREFIX,
message.toChain,
message.callee,
message.sender,
uint16(message.data.length),
message.data
);
}
function parseGenericMessage(
bytes memory encoded
) internal pure returns (GenericMessage memory message) {
uint256 offset = 0;
bytes4 prefix;
(prefix, offset) = encoded.asBytes4Unchecked(offset);
if (prefix != GMP_PREFIX) revert InvalidPrefix();
(message.toChain, offset) = encoded.asUint16Unchecked(offset);
(message.callee, offset) = encoded.asBytes32Unchecked(offset);
(message.sender, offset) = encoded.asBytes32Unchecked(offset);
uint16 dataLength;
(dataLength, offset) = encoded.asUint16Unchecked(offset);
(message.data, offset) = encoded.sliceUnchecked(offset, dataLength);
encoded.checkLength(offset);
}
}
"
},
"src/GmpManager/GmpIntegration.sol": {
"content": "// SPDX-License-Identifier: Apache 2
pragma solidity >=0.8.8 <0.9.0;
import "../interfaces/IGmpManager.sol";
abstract contract GmpIntegration {
IGmpManager public immutable gmpManager;
error OnlyGmpManagerAllowed();
constructor(
IGmpManager _gmpManager
) {
gmpManager = _gmpManager;
}
modifier onlyGmpManager() {
if (msg.sender != address(gmpManager)) revert OnlyGmpManagerAllowed();
_;
}
/// @notice Receive a message via the GMP manager.
/// @dev The GMP manager performs verification and replay protection.
/// @dev `data` is the payload of the message, which is not necessarily
/// unique. `digest` is a unique identifier for the message, which commits
/// to metadata not directly included in `data`.
/// When an integrator wants to uniquely identify a message, they should
/// use `digest` instead of `data`.
function receiveMessage(
bytes32 digest,
uint16 sourceChainId,
bytes32 sender,
bytes calldata data
) external onlyGmpManager {
_receiveMessage(digest, sourceChainId, sender, data);
}
function _receiveMessage(
bytes32 digest,
uint16 sourceChainId,
bytes32 sender,
bytes calldata data
) internal virtual;
function _sendMessage(
uint256 msgValue,
uint16 targetChain,
bytes32 callee,
bytes32 refundAddress,
uint64 reservedSequence,
bytes memory data,
bytes memory transceiverInstructions
) internal returns (uint64 sequence) {
return gmpManager.sendMessage{value: msgValue}(
targetChain, callee, refundAddress, reservedSequence, data, transceiverInstructions
);
}
}
"
},
"src/NttManager/ManagerBase.sol": {
"content": "// SPDX-License-Identifier: Apache 2
pragma solidity >=0.8.8 <0.9.0;
import "wormhole-solidity-sdk/Utils.sol";
import "wormhole-solidity-sdk/libraries/BytesParsing.sol";
import "../libraries/external/OwnableUpgradeable.sol";
import "../libraries/external/ReentrancyGuardUpgradeable.sol";
import "../libraries/TransceiverStructs.sol";
import "../libraries/TransceiverHelpers.sol";
import "../libraries/PausableOwnable.sol";
import "../libraries/Implementation.sol";
import "../interfaces/ITransceiver.sol";
import "../interfaces/IManagerBase.sol";
import "./TransceiverRegistry.sol";
abstract contract ManagerBase is
IManagerBase,
TransceiverRegistry,
PausableOwnable,
ReentrancyGuardUpgradeable,
Implementation
{
// =============== Immutables ============================================================
/// @dev Contract deployer address
address immutable deployer;
/// @dev Wormhole chain ID that the NTT Manager is deployed on.
/// This chain ID is formatted Wormhole Chain IDs -- https://docs.wormhole.com/wormhole/reference/constants
uint16 public immutable chainId;
/// @dev EVM chain ID that the NTT Manager is deployed on.
/// This chain ID is formatted based on standardized chain IDs, e.g. Ethereum mainnet is 1, Sepolia is 11155111, etc.
uint256 immutable evmChainId;
// =============== Setup =================================================================
constructor(
uint16 _chainId
) {
chainId = _chainId;
evmChainId = block.chainid;
// save the deployer (check this on initialization)
deployer = msg.sender;
}
function _migrate() internal virtual override {
// Note: _checkThresholdInvariants() removed since we don't maintain global thresholds
_checkTransceiversInvariants();
}
// =============== Storage ==============================================================
bytes32 private constant MESSAGE_ATTESTATIONS_SLOT =
bytes32(uint256(keccak256("ntt.messageAttestations")) - 1);
bytes32 private constant MESSAGE_SEQUENCE_SLOT =
bytes32(uint256(keccak256("ntt.messageSequence")) - 1);
// Note: THRESHOLD_SLOT removed - we now use per-chain thresholds only
// TODO: come up with a backwards compatible way so this contract can be
// merged upstream (probably some simple abstract class hierarchy)
// =============== Storage Getters/Setters ==============================================
// Note: _getThresholdStorage() removed - we now use per-chain thresholds only
function _getMessageAttestationsStorage()
internal
pure
returns (mapping(bytes32 => AttestationInfo) storage $)
{
uint256 slot = uint256(MESSAGE_ATTESTATIONS_SLOT);
assembly ("memory-safe") {
$.slot := slot
}
}
function _getMessageSequenceStorage() internal pure returns (_Sequence storage $) {
uint256 slot = uint256(MESSAGE_SEQUENCE_SLOT);
assembly ("memory-safe") {
$.slot := slot
}
}
// =============== External Logic =============================================================
function attestationReceived(
uint16 sourceChainId,
bytes32 sourceNttManagerAddress,
TransceiverStructs.NttManagerMessage memory payload
) external onlyTransceiver whenNotPaused {
_verifyPeer(sourceChainId, sourceNttManagerAddress);
// Compute manager message digest and record transceiver attestation.
bytes32 nttManagerMessageHash = _recordTransceiverAttestation(sourceChainId, payload);
if (isMessageApprovedForChain(sourceChainId, nttManagerMessageHash)) {
this.executeMsg(sourceChainId, sourceNttManagerAddress, payload);
}
}
/// @inheritdoc IManagerBase
function quoteDeliveryPrice(
uint16 recipientChain,
bytes memory transceiverInstructions
) public view returns (uint256[] memory, uint256) {
uint256 numRegisteredTransceivers = _getRegisteredTransceiversStorage().length;
address[] memory enabledTransceivers = getSendTransceiversForChain(recipientChain);
TransceiverStructs.TransceiverInstruction[] memory instructions = TransceiverStructs
.parseTransceiverInstructions(transceiverInstructions, numRegisteredTransceivers);
return _quoteDeliveryPrice(recipientChain, instructions, enabledTransceivers);
}
// =============== Internal Logic ===========================================================
function _quoteDeliveryPrice(
uint16 recipientChain,
TransceiverStructs.TransceiverInstruction[] memory transceiverInstructions,
address[] memory enabledTransceivers
) internal view returns (uint256[] memory, uint256) {
uint256 numEnabledTransceivers = enabledTransceivers.length;
mapping(address => TransceiverInfo) storage transceiverInfos = _getTransceiverInfosStorage();
uint256[] memory priceQuotes = new uint256[](numEnabledTransceivers);
uint256 totalPriceQuote = 0;
for (uint256 i = 0; i < numEnabledTransceivers; i++) {
address transceiverAddr = enabledTransceivers[i];
uint8 registeredTransceiverIndex = transceiverInfos[transceiverAddr].index;
uint256 transceiverPriceQuote = ITransceiver(transceiverAddr).quoteDeliveryPrice(
recipientChain, transceiverInstructions[registeredTransceiverIndex]
);
priceQuotes[i] = transceiverPriceQuote;
totalPriceQuote += transceiverPriceQuote;
}
return (priceQuotes, totalPriceQuote);
}
function _recordTransceiverAttestation(
uint16 sourceChainId,
TransceiverStructs.NttManagerMessage memory payload
) internal returns (bytes32) {
bytes32 nttManagerMessageHash =
TransceiverStructs.nttManagerMessageDigest(sourceChainId, payload);
// set the attested flag for this transceiver.
// NOTE: Attestation is idempotent (bitwise or 1), but we revert
// anyway to ensure that the client does not continue to initiate calls
// to receive the same message through the same transceiver.
if (
transceiverAttestedToMessage(
nttManagerMessageHash, _getTransceiverInfosStorage()[msg.sender].index
)
) {
revert TransceiverAlreadyAttestedToMessage(nttManagerMessageHash);
}
_setTransceiverAttestedToMessage(nttManagerMessageHash, msg.sender);
return nttManagerMessageHash;
}
function _isMessageExecuted(
uint16 sourceChainId,
bytes32 sourceNttManagerAddress,
TransceiverStructs.NttManagerMessage memory message
) internal returns (bytes32, bool) {
bytes32 digest = TransceiverStructs.nttManagerMessageDigest(sourceChainId, message);
if (!isMessageApprovedForChain(sourceChainId, digest)) {
revert MessageNotApproved(digest);
}
bool msgAlreadyExecuted = _replayProtect(digest);
if (msgAlreadyExecuted) {
// end execution early to mitigate the possibility of race conditions from transceivers
// attempting to deliver the same message when (threshold < number of transceiver messages)
// notify client (off-chain process) so they don't attempt redundant msg delivery
emit MessageAlreadyExecuted(sourceNttManagerAddress, digest);
return (bytes32(0), msgAlreadyExecuted);
}
return (digest, msgAlreadyExecuted);
}
function _sendMessageToTransceivers(
uint16 recipientChain,
bytes32 refundAddress,
bytes32 peerAddress,
uint256[] memory priceQuotes,
TransceiverStructs.TransceiverInstruction[] memory transceiverInstructions,
address[] memory enabledTransceivers,
bytes memory nttManagerMessage
) internal {
uint256 numEnabledTransceivers = enabledTransceivers.length;
mapping(address => TransceiverInfo) storage transceiverInfos = _getTransceiverInfosStorage();
if (peerAddress == bytes32(0)) {
revert PeerNotRegistered(recipientChain);
}
// push onto the stack again to avoid stack too deep error
bytes32 refundRecipient = refundAddress;
// call into transceiver contracts to send the message
for (uint256 i = 0; i < numEnabledTransceivers; i++) {
address transceiverAddr = enabledTransceivers[i];
// send it to the recipient nttManager based on the chain
ITransceiver(transceiverAddr).sendMessage{value: priceQuotes[i]}(
recipientChain,
transceiverInstructions[transceiverInfos[transceiverAddr].index],
nttManagerMessage,
peerAddress,
refundRecipient
);
}
}
function _prepareForTransfer(
uint16 recipientChain,
bytes memory transceiverInstructions
)
internal
returns (
address[] memory,
TransceiverStructs.TransceiverInstruction[] memory,
uint256[] memory,
uint256
)
{
address[] memory enabledTransceivers = getSendTransceiversForChain(recipientChain);
TransceiverStructs.TransceiverInstruction[] memory instructions;
{
uint256 numRegisteredTransceivers = _getRegisteredTransceiversStorage().length;
uint256 numEnabledTransceivers = enabledTransceivers.length;
if (numEnabledTransceivers == 0) {
revert NoEnabledTransceivers();
}
instructions = TransceiverStructs.parseTransceiverInstructions(
transceiverInstructions, numRegisteredTransceivers
);
}
(uint256[] memory priceQuotes, uint256 totalPriceQuote) =
_quoteDeliveryPrice(recipientChain, instructions, enabledTransceivers);
{
// check up front that msg.value will cover the delivery price
if (msg.value < totalPriceQuote) {
revert DeliveryPaymentTooLow(totalPriceQuote, msg.value);
}
// refund user extra excess value from msg.value
uint256 excessValue = msg.value - totalPriceQuote;
if (excessValue > 0) {
_refundToSender(excessValue);
}
}
return (enabledTransceivers, instructions, priceQuotes, totalPriceQuote);
}
function _refundToSender(
uint256 refundAmount
) internal {
// refund the price quote back to sender
(bool refundSuccessful,) = payable(msg.sender).call{value: refundAmount}("");
// check success
if (!refundSuccessful) {
revert RefundFailed(refundAmount);
}
}
// =============== Public Getters ========================================================
/// @notice Check if a message has enough attestations for a specific source chain
function isMessageApprovedForChain(
uint16 sourceChain,
bytes32 digest
) public view returns (bool) {
uint8 threshold = _getThresholdForChain(sourceChain);
uint8 attestations = messageAttestationsForChain(sourceChain, digest);
return attestations >= threshold && threshold > 0;
}
/// @inheritdoc IManagerBase
function getThreshold(
uint16 sourceChain
) external view returns (uint8) {
return _getThresholdForChain(sourceChain);
}
/// @inheritdoc IManagerBase
function nextMessageSequence() external view returns (uint64) {
return _getMessageSequenceStorage().num;
}
/// @inheritdoc IManagerBase
function isMessageExecuted(
bytes32 digest
) public view returns (bool) {
return _getMessageAttestationsStorage()[digest].executed;
}
/// @inheritdoc IManagerBase
function transceiverAttestedToMessage(bytes32 digest, uint8 index) public view returns (bool) {
return
_getMessageAttestationsStorage()[digest].attestedTransceivers & uint64(1 << index) > 0;
}
/// @inheritdoc IManagerBase
function messageAttestations(
bytes32 digest
) public view returns (uint8 count) {
return countSetBits(_getMessageAttestations(digest));
}
/// @notice Get the number of attestations for a message from a specific source chain
function messageAttestationsForChain(
uint16 sourceChain,
bytes32 digest
) public view returns (uint8 count) {
return countSetBits(_getMessageAttestationsForChain(sourceChain, digest));
}
// =============== Admin ==============================================================
/// @inheritdoc IManagerBase
function upgrade(
address newImplementation
) external onlyOwner {
_upgrade(newImplementation);
}
/// @inheritdoc IManagerBase
function pause() public onlyOwnerOrPauser {
_pause();
}
function unpause() public onlyOwner {
_unpause();
}
/// @notice Transfer ownership of the Manager contract and all Transceiver contracts to a new owner.
function transferOwnership(
address newOwner
) public override onlyOwner {
super.transferOwnership(newOwner);
// loop through all the registered transceivers and set the new owner of each transceiver to the newOwner
address[] storage _registeredTransceivers = _getRegisteredTransceiversStorage();
_checkRegisteredTransceiversInvariants();
for (uint256 i = 0; i < _registeredTransceivers.length; i++) {
address transceiver = _registeredTransceivers[i];
try ITransceiver(transceiver).transferTransceiverOwnership(newOwner) {
// Success - ownership transferred
} catch (bytes memory reason) {
// Failed to transfer ownership - emit event and continue with other transceivers
// This prevents disabled or malfunctioning transceivers from blocking the entire ownership transfer
emit TransceiverOwnershipTransferFailed(transceiver, reason);
}
}
}
/// @inheritdoc IManagerBase
function setTransceiver(
address transceiver
) external onlyOwner {
_setTransceiver(transceiver);
// Note: Global threshold is no longer maintained since we use per-chain thresholds.
// Per-chain thresholds must be configured separately using setThreshold(uint16, uint8).
emit TransceiverAdded(transceiver, _getNumTransceiversStorage().enabled, 0);
// Note: _checkThresholdInvariants() removed since we don't maintain global thresholds
}
/// @inheritdoc IManagerBase
function removeTransceiver(
address transceiver
) external onlyOwner {
uint8 numEnabledTransceivers = _getNumTransceiversStorage().enabled;
// Prevent removing the last transceiver - you need at least one for the system to work
if (numEnabledTransceivers <= 1) {
revert ZeroThreshold(); // Reusing this error since it's about threshold requirements
}
// remove from all per-chain configurations first
_removeTransceiverFromAllChains(transceiver);
// then remove globally
_removeTransceiver(transceiver);
// Note: Global threshold is no longer maintained since we use per-chain thresholds.
// Per-chain thresholds are automatically adjusted in _removeReceiveTransceiverForChain().
emit TransceiverRemoved(transceiver, 0);
}
/// @notice Add a transceiver for sending to a specific chain
/// @param targetChain The chain ID to send to
/// @param transceiver The transceiver to enable for sending to this chain
function setSendTransceiverForChain(
uint16 targetChain,
address transceiver
) external onlyOwner {
_setSendTransceiverForChain(targetChain, transceiver);
emit SendTransceiverUpdatedForChain(targetChain, transceiver, true);
}
/// @notice Remove a transceiver for sending to a specific chain
/// @param targetChain The chain ID
/// @param transceiver The transceiver to disable for sending to this chain
function removeSendTransceiverForChain(
uint16 targetChain,
address transceiver
) external onlyOwner {
_removeSendTransceiverForChain(targetChain, transceiver);
emit SendTransceiverUpdatedForChain(targetChain, transceiver, false);
}
/// @notice Add a transceiver for receiving from a specific chain
/// @param sourceChain The chain ID to receive from
/// @param transceiver The transceiver to enable for receiving from this chain
function setReceiveTransceiverForChain(
uint16 sourceChain,
address transceiver
) external onlyOwner {
_setReceiveTransceiverForChain(sourceChain, transceiver);
emit ReceiveTransceiverUpdatedForChain(sourceChain, transceiver, true);
}
/// @notice Remove a transceiver for receiving from a specific chain
/// @param sourceChain The chain ID
/// @param transceiver The transceiver to disable for receiving from this chain
function removeReceiveTransceiverForChain(
uint16 sourceChain,
address transceiver
) external onlyOwner {
_removeReceiveTransceiverForChain(sourceChain, transceiver);
emit ReceiveTransceiverUpdatedForChain(sourceChain, transceiver, false);
}
/// @notice Set the threshold for receiving from a specific chain
/// @param sourceChain The chain ID
/// @param threshold The threshold for receiving from this chain
function setThreshold(uint16 sourceChain, uint8 threshold) external onlyOwner {
_setThresholdForChain(sourceChain, threshold);
emit ThresholdUpdatedForChain(sourceChain, threshold);
}
/// @notice Register a known chain for migration purposes
/// @dev This function is used to populate the known chains list for existing deployments
/// that were created before the chain registry was introduced. It verifies the peer
/// relationship before adding the chain to ensure only valid chains are registered.
/// This function can be called by anyone since it only adds valid peer chains.
/// @param peerChainId The chain ID to register
/// @param peerAddress The peer address on that chain (used for verification)
function registerKnownChain(uint16 peerChainId, bytes32 peerAddress) external {
if (peerAddress == bytes32(0)) {
revert InvalidPeerZeroAddress();
}
// Verify this is a valid peer relationship
_verifyPeer(peerChainId, peerAddress);
// If verification passes, add to known chains
_addToKnownChains(peerChainId);
}
// =============== Internal ==============================================================
function _verifyPeer(uint16 sourceChainId, bytes32 peerAddress) internal virtual;
function _setTransceiverAttestedToMessage(bytes32 digest, uint8 index) internal {
_getMessageAttestationsStorage()[digest].attestedTransceivers |= uint64(1 << index);
}
function _setTransceiverAttestedToMessage(bytes32 digest, address transceiver) internal {
_setTransceiverAttestedToMessage(digest, _getTransceiverInfosStorage()[transceiver].index);
emit MessageAttestedTo(
digest, transceiver, _getTransceiverInfosStorage()[transceiver].index
);
}
/// @dev Returns the bitmap of attestations from enabled transceivers for a given message.
function _getMessageAttestations(
bytes32 digest
) internal view returns (uint64) {
uint64 enabledTransceiverBitmap = _getEnabledTransceiversBitmap();
return
_getMessageAttestationsStorage()[digest].attestedTransceivers & enabledTransceiverBitmap;
}
/// @dev Returns the bitmap of attestations from enabled transceivers for a given message and source chain.
function _getMessageAttestationsForChain(
uint16 sourceChain,
bytes32 digest
) internal view returns (uint64) {
uint64 enabledTransceiverBitmap = _getReceiveTransceiversBitmapForChain(sourceChain);
return
_getMessageAttestationsStorage()[digest].attestedTransceivers & enabledTransceiverBitmap;
}
function _getEnabledTransceiverAttestedToMessage(
bytes32 digest,
uint8 index
) internal view returns (bool) {
return _getMessageAttestations(digest) & uint64(1 << index) != 0;
}
// @dev Mark a message as executed.
// This function will retuns `true` if the message has already been executed.
function _replayProtect(
bytes32 digest
) internal returns (bool) {
// check if this message has already been executed
if (isMessageExecuted(digest)) {
return true;
}
// mark this message as executed
_getMessageAttestationsStorage()[digest].executed = true;
return false;
}
function _useMessageSequence() internal returns (uint64 currentSequence) {
currentSequence = _getMessageSequenceStorage().num++;
}
/// ============== Invariants =============================================
/// @dev When we add new immutables, this function should be updated
function _checkImmutables() internal view virtual override {
super._checkImmutables();
assert(this.chainId() == chainId);
}
function _checkRegisteredTransceiversInvariants() internal view {
if (_getRegisteredTransceiversStorage().length != _getNumTransceiversStorage().registered) {
revert RetrievedIncorrectRegisteredTransceivers(
_getRegisteredTransceiversStorage().length, _getNumTransceiversStorage().registered
);
}
}
// Note: _checkThresholdInvariants() function removed since we don't maintain global thresholds
// Per-chain threshold validation is handled in _setThresholdForChain()
}
"
},
"lib/wormhole-solidity-sdk/src/Utils.sol": {
"content": "
// SPDX-License-Identifier: Apache 2
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)));
}
"
},
"src/interfaces/IManagerBase.sol": {
"content": "// SPDX-License-Identifier: Apache 2
pragma solidity >=0.8.8 <0.9.0;
import "../libraries/TransceiverStructs.sol";
interface IManagerBase {
/// @notice Information about attestations for a given message.
/// @dev The fields are as follows:
/// - executed: whether the message has been executed.
/// - attested: bitmap of transceivers that have attested to this message.
/// (NOTE: might contain disabled transceivers)
struct AttestationInfo {
bool executed;
uint64 attestedTransceivers;
}
struct _Sequence {
uint64 num;
}
struct _Threshold {
uint8 num;
}
/// @notice Emitted when a message has been attested to.
/// @dev Topic0
/// 0x35a2101eaac94b493e0dfca061f9a7f087913fde8678e7cde0aca9897edba0e5.
/// @param digest The digest of the message.
/// @param transceiver The address of the transceiver.
/// @param index The index of the transceiver in the bitmap.
event MessageAttestedTo(bytes32 digest, address transceiver, uint8 index);
/// @notice Emmitted when the threshold required transceivers is changed.
/// @dev Topic0
/// 0x2a855b929b9a53c6fb5b5ed248b27e502b709c088e036a5aa17620c8fc5085a9.
/// @param oldThreshold The old threshold.
/// @param threshold The new threshold.
event ThresholdChanged(uint8 oldThreshold, uint8 threshold);
/// @notice Emitted when an transceiver is removed from the nttManager.
/// @dev Topic0
/// 0xf05962b5774c658e85ed80c91a75af9d66d2af2253dda480f90bce78aff5eda5.
/// @param transceiver The address of the transceiver.
/// @param transceiversNum The current number of transceivers.
/// @param threshold The current threshold of transceivers.
event TransceiverAdded(address transceiver, uint256 transceiversNum, uint8 threshold);
/// @notice Emitted when an transceiver is removed from the nttManager.
/// @dev Topic0
/// 0x697a3853515b88013ad432f29f53d406debc9509ed6d9313dcfe115250fcd18f.
/// @param transceiver The address of the transceiver.
/// @param threshold The current threshold of transceivers.
event TransceiverRemoved(address transceiver, uint8 threshold);
/// @notice Emitted when a send transceiver is updated for a specific chain.
/// @param targetChain The chain ID.
/// @param transceiver The transceiver address.
/// @param enabled Whether the transceiver is enabled or disabled.
event SendTransceiverUpdatedForChain(uint16 targetChain, address transceiver, bool enabled);
/// @notice Emitted when a receive transceiver is updated for a specific chain.
/// @param sourceChain The chain ID.
/// @param transceiver The transceiver address.
/// @param enabled Whether the transceiver is enabled or disabled.
event ReceiveTransceiverUpdatedForChain(uint16 sourceChain, address transceiver, bool enabled);
/// @notice Emitted when the threshold is updated for a specific chain.
/// @param sourceChain The chain ID.
/// @param threshold The new threshold.
event ThresholdUpdatedForChain(uint16 sourceChain, uint8 threshold);
/// @notice Emitted when a transceiver ownership transfer fails.
/// @param transceiver The transceiver address.
/// @param reason The reason for the failure.
event TransceiverOwnershipTransferFailed(address transceiver, bytes reason);
/// @notice payment for a transfer is too low.
/// @param requiredPayment The required payment.
/// @param providedPayment The provided payment.
error DeliveryPaymentTooLow(uint256 requiredPayment, uint256 providedPayment);
/// @notice Error when the refund to the sender fails.
/// @dev Selector 0x2ca23714.
/// @param refundAmount The refund amount.
error RefundFailed(uint256 refundAmount);
/// @notice The number of thresholds should not be zero.
error ZeroThreshold();
error RetrievedIncorrectRegisteredTransceivers(uint256 retrieved, uint256 registered);
/// @notice The threshold for transceiver attestations is too high.
/// @param threshold The threshold.
/// @param transceivers The number of transceivers.
error ThresholdTooHigh(uint256 threshold, uint256 transceivers);
/// @notice Error when the tranceiver already attested to the message.
/// To ensure the client does not continue to initiate calls to the attestationReceived function.
/// @dev Selector 0x2113894.
/// @param nttManagerMessageHash The hash of the message.
error TransceiverAlreadyAttestedToMessage(bytes32 nttManagerMessageHash);
/// @notice Error when the message is not approved.
/// @dev Selector 0x451c4fb0.
/// @param msgHash The hash of the message.
error MessageNotApproved(bytes32 msgHash);
/// @notice Emitted when a message has already been executed to
/// notify client of against retries.
/// @dev Topic0
/// 0x4069dff8c9df7e38d2867c0910bd96fd61787695e5380281148c04932d02bef2.
/// @param sourceNttManager The address of the source nttManager.
/// @param msgHash The keccak-256 hash of the message.
event MessageAlreadyExecuted(bytes32 indexed sourceNttManager, bytes32 indexed msgHash);
/// @notice There are no transceivers enabled with the Manager
/// @dev Selector 0x69cf632a
error NoEnabledTransceivers();
/// @notice Error when the recipient is invalid.
/// @dev Selector 0xe2fe2726.
error InvalidRefundAddress();
/// @notice Peer chain ID cannot be zero.
error InvalidPeerChainIdZero();
/// @notice Peer cannot be the zero address.
error InvalidPeerZeroAddress();
/// @notice Peer cannot be on the same chain
/// @dev Selector 0x20371f2a.
error InvalidPeerSameChainId();
/// @notice Peer for the chain does not match the configuration.
/// @param chainId ChainId of the source chain.
/// @param peerAddress Address of the peer nttManager contract.
error InvalidPeer(uint16 chainId, bytes32 peerAddress);
/// @notice Error when the manager doesn't have a peer registered for the destination chain
/// @dev Selector 0x3af256bc.
/// @param chainId The target Wormhole chain id
error PeerNotRegistered(uint16 chainId);
/// @notice Called by a Transceiver contract to deliver a verified attestation.
/// @dev This function enforces attestation threshold and replay logic for messages. Once all
/// validations are complete, this function calls `executeMsg` to execute the command specified
/// by the message.
/// @param sourceChainId The Wormhole chain id of the sender.
/// @param sourceNttManagerAddress The address of the sender's NTT Manager contract.
/// @param payload The VAA payload.
function attestationReceived(
uint16 sourceChainId,
bytes32 sourceNttManagerAddress,
TransceiverStructs.NttManagerMessage memory payload
) external;
/// @notice Called after a message has been sufficiently verified to execute
/// the command in the message.
/// @dev This function is exposed as a fallback for when an `Transceiver` is deregistered
/// when a message is in flight.
/// @param sourceChainId The Wormhole chain id of the sender.
/// @param sourceNttManagerAddress The address of the sender's nttManager contract.
/// @param message The message to execute.
function executeMsg(
uint16 sourceChainId,
bytes32 sourceNttManagerAddress,
TransceiverStructs.NttManagerMessage memory message
) external;
/// @notice Fetch the delivery price for a given recipient chain transfer.
/// @param recipientChain The Wormhole chain ID of the transfer destination.
/// @param transceiverInstructions The transceiver specific instructions for quoting and sending
/// @return - The delivery prices associated with each enabled endpoint and the total price.
function quoteDeliveryPrice(
uint16 recipientChain,
bytes memory transceiverInstructions
) external view returns (uint256[] memory, uint256);
/// @notice Sets the threshold for the number of attestations required for a message
/// from a specific source chain to be considered valid.
/// @param sourceChain The chain ID to set the threshold for.
/// @param threshold The new threshold (number of attestations).
/// @dev This method can only be executed by the `owner`.
function setThreshold(uint16 sourceChain, uint8 threshold) external;
/// @notice Sets the transceiver.
/// @param transceiver The address of the transceiver.
/// @dev This method can only be executed by the `owner`.
function setTransceiver(
address transceiver
) external;
/// @notice Removes the transceiver.
/// @param transceiver The address of the transceiver.
/// @dev This method can only be executed by the `owner`.
function removeTransceiver(
address transceiver
) external;
/// @param sourceChain The chain ID of the message source.
/// @param digest The digest of the message.
/// @return approved Boolean indicating if the message is approved for execution.
function isMessageApprovedForChain(
uint16 sourceChain,
bytes32 digest
) external view returns (bool approved);
/// @notice Checks if a message has been executed.
/// @param digest The digest of the message.
/// @return - Boolean indicating if message has been executed.
function isMessageExecuted(
bytes32 digest
) external view returns (bool);
/// @notice Returns the next message sequence.
function nextMessageSequence() external view returns (uint64);
/// @notice Upgrades to a new manager implementation.
/// @dev This is upgraded via a proxy, and can only be executed
/// by the `owner`.
/// @param newImplementation The address of the new implementation.
function upgrade(
address newImplementation
) external;
/// @notice Pauses the manager.
function pause() external;
/// @notice Returns the number of Transceivers that must attest to a msgId for
/// it to be considered valid and acted upon from a particular source chain.
/// @param sourceChain The chain ID.
/// @return threshold The threshold for this chain.
function getThreshold(
uint16 sourceChain
) external view returns (uint8 threshold);
/// @notice Returns a boolean indicating if the transceiver has attested to the message.
/// @param digest The digest of the message.
/// @param index The index of the transceiver
/// @return - Boolean indicating whether the transceiver at index `index` attested to a message digest
function transceiverAttestedToMessage(
bytes32 digest,
uint8 index
) external view returns (bool);
/// @notice Returns the number of attestations for a given message.
/// @param digest The digest of the message.
/// @return count The number of attestations received for the given message digest
function messageAttestations(
bytes32 digest
) external view returns (uint8 count);
/// @notice Returns the chain ID.
function chainId() external view returns (uint16);
}
"
},
"lib/wormhole-solidity-sdk/src/libraries/BytesParsing.sol": {
"content": "// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.13;
library BytesParsing {
uint256 private constant freeMemoryPtr = 0x40;
uint256 private constant wordSize = 32;
error OutOfBounds(uint256 offset, uint256 length);
error LengthMismatch(uint256 encodedLength, uint256 expectedLength);
error InvalidBoolVal(uint8 val);
function checkBound(uint offset, uint length) internal pure {
if (offset > length)
revert OutOfBounds(offset, length);
}
function checkLength(bytes memory encoded, uint256 expected) internal pure {
if (encoded.length != expected)
revert LengthMismatch(encoded.length, expected);
}
function sliceUnchecked(
bytes memory encoded,
uint offset,
uint length
) internal pure returns (bytes memory ret, uint nextOffset) {
//bail early for degenerate case
if (length == 0)
return (new bytes(0), offset);
assembly ("memory-safe") {
nextOffset := add(offset, length)
ret := mload(freeMemoryPtr)
//Explanation on how we copy data here:
// The bytes type has the following layout in memory:
// [length: 32 bytes, data: length bytes]
// So if we allocate `bytes memory foo = new bytes(1);` then `foo` will be a pointer to 33
// bytes where the first 32 bytes contain the length and the last byte is the actual data.
// Since mload always loads 32 bytes of memory at once, we use our shift variable to align
// our reads so that our last read lines up exactly with the last 32 bytes of `encoded`.
// However this also means that if the length of `encoded` is not a multiple of 32 bytes, our
// first read will necessarily partly contain bytes from `encoded`'s 32 length bytes that
// will be written into the length part of our `ret` slice.
// We remedy this issue by writing the length of our `ret` slice at the end, thus
// overwritting those garbage bytes.
let shift := and(length, 31) //equivalent to `mod(length, 32)` but 2 gas cheaper
if iszero(shift) {
shift := wordSize
}
let dest := add(ret, shift)
let end := add(dest, length)
for {
let src := add(add(encoded, shift), offset)
} lt(dest, end) {
src := add(src, wordSize)
dest := add(dest, wordSize)
} {
mstore(dest, mload(src))
}
mstore(ret, length)
//When compiling with --via-ir then normally allocated memory (i.e. via new) will have 32 byte
// memory alignment and so we enforce the same memory alignment here.
mstore(freeMemoryPtr, and(add(dest, 31), not(31)))
}
}
function slice(
bytes memory encoded,
uint offset,
uint length
) internal pure returns (bytes memory ret, uint nextOffset) {
(ret, nextOffset) = sliceUnchecked(encoded, offset, length);
checkBound(nextOffset, encoded.length);
}
function asAddressUnchecked(
bytes memory encoded,
uint offset
) internal pure returns (address, uint) {
(uint160 ret, uint nextOffset) = asUint160Unchecked(encoded, offset);
return (address(ret), nextOffset);
}
function asAddress(
bytes memory encoded,
uint offset
) internal pure returns (address ret, uint nextOffset) {
(ret, nextOffset) = asAddressUnchecked(encoded, offset);
checkBound(nextOffset, encoded.length);
}
function asBoolUnchecked(
bytes memory encoded,
uint offset
) internal pure returns (bool, uint) {
(uint8 val, uint nextOffset) = asUint8Unchecked(encoded, offset);
if (val & 0xfe != 0)
revert InvalidBoolVal(val);
uint cleanedVal = uint(val);
bool ret;
//skip 2x iszero opcode
assembly ("memory-safe") {
ret := cleanedVal
}
return (ret, nextOffset);
}
function asBool(
bytes memory encoded,
uint offset
) internal pure returns (bool ret, uint nextOffset) {
(ret, nextOffset) = asBoolUnchecked(encoded, offset);
checkBound(nextOffset, encoded.length);
}
/* -------------------------------------------------------------------------------------------------
Remaining library code below was auto-generated by via the following js/node code:
for (let bytes = 1; bytes <= 32; ++bytes) {
const bits = bytes*8;
console.log(
`function asUint${bits}Unchecked(
bytes memory encoded,
uint offset
) internal pure returns (uint${bits} ret, uint nextOffset) {
assembly ("memory-safe") {
nextOffset := add(offset, ${bytes})
ret := mload(add(encoded, nextOffset))
}
return (ret, nextOffset);
}
function asUint${bits}(
bytes memory encoded,
uint offset
) internal pure returns (uint${bits} ret, uint nextOffset) {
(ret, nextOffset) = asUint${bits}Unchecked(encoded, offset);
checkBound(nextOffset, encoded.length);
}
function asBytes${bytes}Unchecked(
bytes memory encoded,
uint offset
) internal pure returns (bytes${bytes}, uint) {
(uint${bits} ret, uint nextOffset) = asUint${bits}Unchecked(encoded, offset);
return (bytes${bytes}(ret), nextOffset);
}
function asBytes${bytes}(
bytes memory encoded,
uint offset
) internal pure returns (bytes${bytes}, uint) {
(uint${bits} ret, uint nextOffset) = asUint${bits}(encoded, offset);
return (bytes${bytes}(ret), nextOffset);
}
`
);
}
------------------------------------------------------------------------------------------------- */
function asUint8Unchecked(
bytes memory encoded,
uint offset
) internal pure returns (uint8 ret, uint nextOffset) {
assembly ("memory-safe") {
nextOffset := add(offset, 1)
ret := mload(add(encoded, nextOffset))
}
return (ret, nextOffset);
}
function asUint8(
bytes memory encoded,
uint offset
) internal pure returns (uint8 ret, uint nextOffset) {
(ret, nextOffset) = asUint8Unchecked(encoded, offset);
checkBound(nextOffset, encoded.length);
}
function asBytes1Unchecked(
bytes memory encoded,
uint offset
) internal pure returns (bytes1, uint) {
(uint8 ret, uint nextOffset) = asUint8Unchecked(encoded, offset);
return (bytes1(ret), nextOffset);
}
function asBytes1(
bytes memory encoded,
uint offset
) internal pure returns (bytes1, uint) {
(uint8 ret, uint nextOffset) = asUint8(encoded, offset);
return (bytes1(ret), nextOffset);
}
function asUint16Unchecked(
bytes memory encoded,
uint offset
) internal pure returns (uint16 ret, uint nextOffset) {
assembly ("memory-safe") {
nextOffset := add(offset, 2)
ret := mload(add(encoded, nextOffset))
}
return (ret, nextOffset);
}
function asUint16(
bytes memory encoded,
uint offset
) internal pure returns (uint16 ret, uint nextOffset) {
(ret, nextOffset) = asUint16Unchecked(encoded, offset);
checkBound(nextOffset, encoded.length);
}
function asBytes2Unchecked(
bytes memory encoded,
uint offset
) internal pure returns (bytes2, uint) {
(uint16 ret, uint nextOffset) = asUint16Unchecked(encoded, offset);
return (bytes2(ret), nextOffset);
}
function asBytes2(
bytes memory encoded,
uint offset
) internal pure returns (bytes2, uint) {
(uint16 ret, uint nextOffset) = asUint16(encoded, offset);
return (bytes2(ret), nextOffset);
}
function asUint24Unchecked(
bytes memory encoded,
uint offset
) internal pure returns (uint24 ret, uint nextOffset) {
assembly ("memory-safe") {
nextOffset := add(offset, 3)
ret := mload(add(encoded, nextOffset))
}
return (ret, nextOffset);
}
function asUint24(
bytes memory encoded,
uint offset
) internal pure returns (uint24 ret, uint nextOffset) {
(ret, nextOffset) = asUint24Unchecked(encoded, offset);
checkBound(nextOffset, encoded.length);
}
function asBytes3Unchecked(
bytes memory encoded,
uint offset
) internal pure returns (bytes3, uint) {
(uint24 ret, uint nextOffset) = asUint24Unchecked(encoded, offset);
return (bytes3(ret), nextOffset);
}
function asBytes3(
bytes memory encoded,
uint offset
) internal pure returns (bytes3, uint) {
(uint24 ret, uint nextOffset) = asUint24(encoded, offset);
return (bytes3(ret), nextOffset);
}
function asUint32Unchecked(
bytes memory encoded,
uint offset
) internal pure returns (uint32 ret, uint nextOffset) {
assembly ("memory-safe") {
nextOffset := add(offset, 4)
ret := mload(add(encoded, nextOffset))
}
return (ret, nextOffset);
}
function asUint32(
bytes memory encoded,
uint offset
) internal pure returns (uint32 ret, uint nextOffset) {
(ret, nextOffset) = asUint32Unchecked(encoded, offset);
checkBound(nextOffset, encoded.length);
}
function asBytes4Unchecked(
bytes memory encoded,
uint offset
) internal pure returns (bytes4, uint) {
(uint32 ret, uint nextOffset) = asUint32Unchecked(encoded, offset);
return (bytes4(ret), nextOffset);
}
function asBytes4(
bytes memory encoded,
uint offset
) internal pure returns (bytes4, uint) {
(uint32 ret, uint nextOffset) = asUint32(encoded, offset);
return (bytes4(ret), nextOffset);
}
function asUint40Unchecked(
bytes memory encoded,
uint offset
) internal pure returns (uint40 ret, uint nextOffset) {
assembly ("memory-safe") {
nextOffset := add(offset, 5)
ret := mload(add(encoded, nextOffset))
}
return (ret, nextOffset);
}
function asUint40(
bytes memory encoded,
uint offset
) internal pure returns (uint40 ret, uint nextOffset) {
(ret, nextOffset) = asUint40Unchecked(encoded, offset);
checkBound(nextOffset, encoded.length);
}
function asBytes5Unchecked(
bytes memory encoded,
uint offset
) internal pure returns (bytes5, uint) {
(uint40 ret, uint nextOffset) = asUint40Unchecked(encoded, offset);
return (bytes5(ret), nextOffset);
}
function asBytes5(
bytes memory encoded,
uint offset
) internal pure returns (bytes5, uint) {
(uint40 ret, uint nextOffset) = asUint40(encoded, offset);
return (bytes5(ret), nextOffset);
}
function asUint48Unchecked(
bytes memory encoded,
uint offset
) internal pure returns (uint48 ret, uint nextOffset) {
assembly ("memory-safe") {
nextOffset := add(offset, 6)
ret := mload(add(encoded, nextOffset))
}
return (ret, nextOffset);
}
function asUint48(
bytes memory encoded,
uint offset
) internal pure returns (uint48 ret, uint nextOffset) {
(ret, nextOffset) = asUint48Unchecked(encoded, offset);
checkBound(nextOffset, encoded.length);
}
function asBytes6Unchecked(
bytes memory encoded,
uint offset
) internal pure returns (bytes6, uint) {
(uint48 ret, uint nextOffset) = asUint48Unchecked(encoded, offset);
return (bytes6(ret), nextOffset);
}
function asBytes6(
bytes memory encoded,
uint offset
) internal pure returns (bytes6, uint) {
(uint48 ret, uint nextOffset) = asUint48(encoded, offset);
return (bytes6(ret), nextOffset);
}
function asUint56Unchecked(
bytes memory encoded,
uint offset\
Submitted on: 2025-11-04 09:57:08
Comments
Log in to comment.
No comments yet.