packages/contracts-rfq/contracts/libs/BridgeTransactionV2.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {IFastBridgeV2} from "../interfaces/IFastBridgeV2.sol";
// solhint-disable no-inline-assembly
library BridgeTransactionV2Lib {
uint16 internal constant VERSION = 2;
// Offsets of the fields in the packed BridgeTransactionV2 struct
// uint16 version [000 .. 002)
// uint32 originChainId [002 .. 006)
// uint32 destChainId [006 .. 010)
// address originSender [010 .. 030)
// address destRecipient [030 .. 050)
// address originToken [050 .. 070)
// address destToken [070 .. 090)
// uint256 originAmount [090 .. 122)
// uint256 destAmount [122 .. 154)
// uint256 originFeeAmount [154 .. 186)
// uint256 deadline [186 .. 218)
// uint256 nonce [218 .. 250)
// address exclusivityRelayer [250 .. 270)
// uint256 exclusivityEndTime [270 .. 302)
// uint256 zapNative [302 .. 334)
// bytes zapData [334 .. ***)
// forgefmt: disable-start
uint256 private constant OFFSET_ORIGIN_CHAIN_ID = 2;
uint256 private constant OFFSET_DEST_CHAIN_ID = 6;
uint256 private constant OFFSET_ORIGIN_SENDER = 10;
uint256 private constant OFFSET_DEST_RECIPIENT = 30;
uint256 private constant OFFSET_ORIGIN_TOKEN = 50;
uint256 private constant OFFSET_DEST_TOKEN = 70;
uint256 private constant OFFSET_ORIGIN_AMOUNT = 90;
uint256 private constant OFFSET_DEST_AMOUNT = 122;
uint256 private constant OFFSET_ORIGIN_FEE_AMOUNT = 154;
uint256 private constant OFFSET_DEADLINE = 186;
uint256 private constant OFFSET_NONCE = 218;
uint256 private constant OFFSET_EXCLUSIVITY_RELAYER = 250;
uint256 private constant OFFSET_EXCLUSIVITY_END_TIME = 270;
uint256 private constant OFFSET_ZAP_NATIVE = 302;
uint256 private constant OFFSET_ZAP_DATA = 334;
// forgefmt: disable-end
error BridgeTransactionV2__InvalidEncodedTx();
error BridgeTransactionV2__UnsupportedVersion(uint16 version);
/// @notice Validates that the encoded transaction is a tightly packed encoded payload for BridgeTransactionV2.
/// @dev Checks the minimum length and the version, use this function before decoding any of the fields.
function validateV2(bytes calldata encodedTx) internal pure {
// Check the minimum length: must at least include all static fields.
if (encodedTx.length < OFFSET_ZAP_DATA) revert BridgeTransactionV2__InvalidEncodedTx();
// Once we validated the length, we can be sure that the version field is present.
uint16 version_ = version(encodedTx);
if (version_ != VERSION) revert BridgeTransactionV2__UnsupportedVersion(version_);
}
/// @notice Encodes the BridgeTransactionV2 struct by tightly packing the fields.
/// @dev `abi.decode` will not work as a result of the tightly packed fields. Use `decodeV2` to decode instead.
function encodeV2(IFastBridgeV2.BridgeTransactionV2 memory bridgeTx) internal pure returns (bytes memory) {
// We split the encoding into two parts to avoid stack-too-deep error
bytes memory firstPart = abi.encodePacked(
VERSION,
bridgeTx.originChainId,
bridgeTx.destChainId,
bridgeTx.originSender,
bridgeTx.destRecipient,
bridgeTx.originToken,
bridgeTx.destToken,
bridgeTx.originAmount
);
return abi.encodePacked(
firstPart,
bridgeTx.destAmount,
bridgeTx.originFeeAmount,
// Note: we skip the deprecated `sendChainGas` flag, which was present in BridgeTransaction V1
bridgeTx.deadline,
bridgeTx.nonce,
// New V2 fields: exclusivity
bridgeTx.exclusivityRelayer,
bridgeTx.exclusivityEndTime,
// New V2 fields: Zap
bridgeTx.zapNative,
bridgeTx.zapData
);
}
/// @notice Decodes the BridgeTransactionV2 struct from the encoded transaction.
/// @dev Encoded BridgeTransactionV2 struct must be tightly packed.
/// Use `validateV2` before decoding to ensure the encoded transaction is valid.
function decodeV2(bytes calldata encodedTx)
internal
pure
returns (IFastBridgeV2.BridgeTransactionV2 memory bridgeTx)
{
bridgeTx.originChainId = originChainId(encodedTx);
bridgeTx.destChainId = destChainId(encodedTx);
bridgeTx.originSender = originSender(encodedTx);
bridgeTx.destRecipient = destRecipient(encodedTx);
bridgeTx.originToken = originToken(encodedTx);
bridgeTx.destToken = destToken(encodedTx);
bridgeTx.originAmount = originAmount(encodedTx);
bridgeTx.destAmount = destAmount(encodedTx);
bridgeTx.originFeeAmount = originFeeAmount(encodedTx);
bridgeTx.deadline = deadline(encodedTx);
bridgeTx.nonce = nonce(encodedTx);
bridgeTx.exclusivityRelayer = exclusivityRelayer(encodedTx);
bridgeTx.exclusivityEndTime = exclusivityEndTime(encodedTx);
bridgeTx.zapNative = zapNative(encodedTx);
bridgeTx.zapData = zapData(encodedTx);
}
/// @notice Extracts the version from the encoded transaction.
function version(bytes calldata encodedTx) internal pure returns (uint16 version_) {
// Load 32 bytes from the start and shift it 240 bits to the right to get the highest 16 bits.
assembly {
version_ := shr(240, calldataload(encodedTx.offset))
}
}
/// @notice Extracts the origin chain ID from the encoded transaction.
function originChainId(bytes calldata encodedTx) internal pure returns (uint32 originChainId_) {
// Load 32 bytes from the offset and shift it 224 bits to the right to get the highest 32 bits.
assembly {
originChainId_ := shr(224, calldataload(add(encodedTx.offset, OFFSET_ORIGIN_CHAIN_ID)))
}
}
/// @notice Extracts the destination chain ID from the encoded transaction.
function destChainId(bytes calldata encodedTx) internal pure returns (uint32 destChainId_) {
// Load 32 bytes from the offset and shift it 224 bits to the right to get the highest 32 bits.
assembly {
destChainId_ := shr(224, calldataload(add(encodedTx.offset, OFFSET_DEST_CHAIN_ID)))
}
}
/// @notice Extracts the origin sender from the encoded transaction.
function originSender(bytes calldata encodedTx) internal pure returns (address originSender_) {
// Load 32 bytes from the offset and shift it 96 bits to the right to get the highest 160 bits.
assembly {
originSender_ := shr(96, calldataload(add(encodedTx.offset, OFFSET_ORIGIN_SENDER)))
}
}
/// @notice Extracts the destination recipient from the encoded transaction.
function destRecipient(bytes calldata encodedTx) internal pure returns (address destRecipient_) {
// Load 32 bytes from the offset and shift it 96 bits to the right to get the highest 160 bits.
assembly {
destRecipient_ := shr(96, calldataload(add(encodedTx.offset, OFFSET_DEST_RECIPIENT)))
}
}
/// @notice Extracts the origin token from the encoded transaction.
function originToken(bytes calldata encodedTx) internal pure returns (address originToken_) {
// Load 32 bytes from the offset and shift it 96 bits to the right to get the highest 160 bits.
assembly {
originToken_ := shr(96, calldataload(add(encodedTx.offset, OFFSET_ORIGIN_TOKEN)))
}
}
/// @notice Extracts the destination token from the encoded transaction.
function destToken(bytes calldata encodedTx) internal pure returns (address destToken_) {
// Load 32 bytes from the offset and shift it 96 bits to the right to get the highest 160 bits.
assembly {
destToken_ := shr(96, calldataload(add(encodedTx.offset, OFFSET_DEST_TOKEN)))
}
}
/// @notice Extracts the origin amount from the encoded transaction.
function originAmount(bytes calldata encodedTx) internal pure returns (uint256 originAmount_) {
// Load 32 bytes from the offset. No shift is applied, as we need the full 256 bits.
assembly {
originAmount_ := calldataload(add(encodedTx.offset, OFFSET_ORIGIN_AMOUNT))
}
}
/// @notice Extracts the destination amount from the encoded transaction.
function destAmount(bytes calldata encodedTx) internal pure returns (uint256 destAmount_) {
// Load 32 bytes from the offset. No shift is applied, as we need the full 256 bits.
assembly {
destAmount_ := calldataload(add(encodedTx.offset, OFFSET_DEST_AMOUNT))
}
}
/// @notice Extracts the origin fee amount from the encoded transaction.
function originFeeAmount(bytes calldata encodedTx) internal pure returns (uint256 originFeeAmount_) {
// Load 32 bytes from the offset. No shift is applied, as we need the full 256 bits.
assembly {
originFeeAmount_ := calldataload(add(encodedTx.offset, OFFSET_ORIGIN_FEE_AMOUNT))
}
}
/// @notice Extracts the deadline from the encoded transaction.
function deadline(bytes calldata encodedTx) internal pure returns (uint256 deadline_) {
// Load 32 bytes from the offset. No shift is applied, as we need the full 256 bits.
assembly {
deadline_ := calldataload(add(encodedTx.offset, OFFSET_DEADLINE))
}
}
/// @notice Extracts the nonce from the encoded transaction.
function nonce(bytes calldata encodedTx) internal pure returns (uint256 nonce_) {
// Load 32 bytes from the offset. No shift is applied, as we need the full 256 bits.
assembly {
nonce_ := calldataload(add(encodedTx.offset, OFFSET_NONCE))
}
}
/// @notice Extracts the exclusivity relayer from the encoded transaction.
function exclusivityRelayer(bytes calldata encodedTx) internal pure returns (address exclusivityRelayer_) {
// Load 32 bytes from the offset and shift it 96 bits to the right to get the highest 160 bits.
assembly {
exclusivityRelayer_ := shr(96, calldataload(add(encodedTx.offset, OFFSET_EXCLUSIVITY_RELAYER)))
}
}
/// @notice Extracts the exclusivity end time from the encoded transaction.
function exclusivityEndTime(bytes calldata encodedTx) internal pure returns (uint256 exclusivityEndTime_) {
// Load 32 bytes from the offset. No shift is applied, as we need the full 256 bits.
assembly {
exclusivityEndTime_ := calldataload(add(encodedTx.offset, OFFSET_EXCLUSIVITY_END_TIME))
}
}
/// @notice Extracts the Zap's native value from the encoded transaction.
function zapNative(bytes calldata encodedTx) internal pure returns (uint256 zapNative_) {
// Load 32 bytes from the offset. No shift is applied, as we need the full 256 bits.
assembly {
zapNative_ := calldataload(add(encodedTx.offset, OFFSET_ZAP_NATIVE))
}
}
/// @notice Extracts the Zap's data from the encoded transaction.
function zapData(bytes calldata encodedTx) internal pure returns (bytes calldata zapData_) {
zapData_ = encodedTx[OFFSET_ZAP_DATA:];
}
}