wormhole/ethereum/contracts/relayer/wormholeRelayer/WormholeRelayerSerde.sol

240 lines
8.9 KiB
Solidity

// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.19;
import {
InvalidPayloadId,
InvalidPayloadLength,
InvalidVaaKeyType,
VaaKey
} from "../../interfaces/relayer/IWormholeRelayerTyped.sol";
import {
DeliveryOverride,
DeliveryInstruction,
RedeliveryInstruction
} from "../../libraries/relayer/RelayerInternalStructs.sol";
import {BytesParsing} from "../../libraries/relayer/BytesParsing.sol";
import "../../interfaces/relayer/TypedUnits.sol";
library WormholeRelayerSerde {
using BytesParsing for bytes;
using WeiLib for Wei;
using GasLib for Gas;
//The slightly subtle difference between `PAYLOAD_ID`s and `VERSION`s is that payload ids carry
// both type information _and_ version information, while `VERSION`s only carry the latter.
//That is, when deserialing a "version struct" we already know the expected type, but since we
// publish both Delivery _and_ Redelivery instructions as serialized messages, we need a robust
// way to distinguish both their type and their version during deserialization.
uint8 private constant VERSION_VAAKEY = 1;
uint8 private constant VERSION_DELIVERY_OVERRIDE = 1;
uint8 private constant PAYLOAD_ID_DELIVERY_INSTRUCTION = 1;
uint8 private constant PAYLOAD_ID_REDELIVERY_INSTRUCTION = 2;
// ---------------------- "public" (i.e implicitly internal) encode/decode -----------------------
//TODO GAS OPTIMIZATION: All the recursive abi.encodePacked calls in here are _insanely_ gas
// inefficient (unless the optimizer is smart enough to just concatenate them tail-recursion
// style which seems highly unlikely)
function encode(DeliveryInstruction memory strct)
internal
pure
returns (bytes memory encoded)
{
encoded = abi.encodePacked(
PAYLOAD_ID_DELIVERY_INSTRUCTION,
strct.targetChain,
strct.targetAddress,
encodeBytes(strct.payload),
strct.requestedReceiverValue,
strct.extraReceiverValue
);
encoded = abi.encodePacked(
encoded,
encodeBytes(strct.encodedExecutionInfo),
strct.refundChain,
strct.refundAddress,
strct.refundDeliveryProvider,
strct.sourceDeliveryProvider,
strct.senderAddress,
encodeVaaKeyArray(strct.vaaKeys)
);
}
function decodeDeliveryInstruction(bytes memory encoded)
internal
pure
returns (DeliveryInstruction memory strct)
{
uint256 offset = checkUint8(encoded, 0, PAYLOAD_ID_DELIVERY_INSTRUCTION);
uint256 requestedReceiverValue;
uint256 extraReceiverValue;
(strct.targetChain, offset) = encoded.asUint16Unchecked(offset);
(strct.targetAddress, offset) = encoded.asBytes32Unchecked(offset);
(strct.payload, offset) = decodeBytes(encoded, offset);
(requestedReceiverValue, offset) = encoded.asUint256Unchecked(offset);
(extraReceiverValue, offset) = encoded.asUint256Unchecked(offset);
(strct.encodedExecutionInfo, offset) = decodeBytes(encoded, offset);
(strct.refundChain, offset) = encoded.asUint16Unchecked(offset);
(strct.refundAddress, offset) = encoded.asBytes32Unchecked(offset);
(strct.refundDeliveryProvider, offset) = encoded.asBytes32Unchecked(offset);
(strct.sourceDeliveryProvider, offset) = encoded.asBytes32Unchecked(offset);
(strct.senderAddress, offset) = encoded.asBytes32Unchecked(offset);
(strct.vaaKeys, offset) = decodeVaaKeyArray(encoded, offset);
strct.requestedReceiverValue = TargetNative.wrap(requestedReceiverValue);
strct.extraReceiverValue = TargetNative.wrap(extraReceiverValue);
checkLength(encoded, offset);
}
function encode(RedeliveryInstruction memory strct)
internal
pure
returns (bytes memory encoded)
{
bytes memory vaaKey = encodeVaaKey(strct.deliveryVaaKey);
encoded = abi.encodePacked(
PAYLOAD_ID_REDELIVERY_INSTRUCTION,
vaaKey,
strct.targetChain,
strct.newRequestedReceiverValue,
encodeBytes(strct.newEncodedExecutionInfo),
strct.newSourceDeliveryProvider,
strct.newSenderAddress
);
}
function decodeRedeliveryInstruction(bytes memory encoded)
internal
pure
returns (RedeliveryInstruction memory strct)
{
uint256 offset = checkUint8(encoded, 0, PAYLOAD_ID_REDELIVERY_INSTRUCTION);
uint256 newRequestedReceiverValue;
(strct.deliveryVaaKey, offset) = decodeVaaKey(encoded, offset);
(strct.targetChain, offset) = encoded.asUint16Unchecked(offset);
(newRequestedReceiverValue, offset) = encoded.asUint256Unchecked(offset);
(strct.newEncodedExecutionInfo, offset) = decodeBytes(encoded, offset);
(strct.newSourceDeliveryProvider, offset) = encoded.asBytes32Unchecked(offset);
(strct.newSenderAddress, offset) = encoded.asBytes32Unchecked(offset);
strct.newRequestedReceiverValue = TargetNative.wrap(newRequestedReceiverValue);
checkLength(encoded, offset);
}
function encode(DeliveryOverride memory strct) internal pure returns (bytes memory encoded) {
encoded = abi.encodePacked(
VERSION_DELIVERY_OVERRIDE,
strct.newReceiverValue,
encodeBytes(strct.newExecutionInfo),
strct.redeliveryHash
);
}
function decodeDeliveryOverride(bytes memory encoded)
internal
pure
returns (DeliveryOverride memory strct)
{
uint256 offset = checkUint8(encoded, 0, VERSION_DELIVERY_OVERRIDE);
uint256 receiverValue;
(receiverValue, offset) = encoded.asUint256Unchecked(offset);
(strct.newExecutionInfo, offset) = decodeBytes(encoded, offset);
(strct.redeliveryHash, offset) = encoded.asBytes32Unchecked(offset);
strct.newReceiverValue = TargetNative.wrap(receiverValue);
checkLength(encoded, offset);
}
// ------------------------------------------ private --------------------------------------------
function encodeVaaKeyArray(VaaKey[] memory vaaKeys)
private
pure
returns (bytes memory encoded)
{
assert(vaaKeys.length < type(uint8).max);
encoded = abi.encodePacked(uint8(vaaKeys.length));
for (uint256 i = 0; i < vaaKeys.length;) {
encoded = abi.encodePacked(encoded, encodeVaaKey(vaaKeys[i]));
unchecked {
++i;
}
}
}
function decodeVaaKeyArray(
bytes memory encoded,
uint256 startOffset
) private pure returns (VaaKey[] memory vaaKeys, uint256 offset) {
uint8 vaaKeysLength;
(vaaKeysLength, offset) = encoded.asUint8Unchecked(startOffset);
vaaKeys = new VaaKey[](vaaKeysLength);
for (uint256 i = 0; i < vaaKeys.length;) {
(vaaKeys[i], offset) = decodeVaaKey(encoded, offset);
unchecked {
++i;
}
}
}
function encodeVaaKey(VaaKey memory vaaKey) private pure returns (bytes memory encoded) {
encoded = abi.encodePacked(
encoded, VERSION_VAAKEY, vaaKey.chainId, vaaKey.emitterAddress, vaaKey.sequence
);
}
function decodeVaaKey(
bytes memory encoded,
uint256 startOffset
) private pure returns (VaaKey memory vaaKey, uint256 offset) {
offset = checkUint8(encoded, startOffset, VERSION_VAAKEY);
(vaaKey.chainId, offset) = encoded.asUint16Unchecked(offset);
(vaaKey.emitterAddress, offset) = encoded.asBytes32Unchecked(offset);
(vaaKey.sequence, offset) = encoded.asUint64Unchecked(offset);
}
function encodeBytes(bytes memory payload) private pure returns (bytes memory encoded) {
//casting payload.length to uint32 is safe because you'll be hard-pressed to allocate 4 GB of
// EVM memory in a single transaction
encoded = abi.encodePacked(uint32(payload.length), payload);
}
function decodeBytes(
bytes memory encoded,
uint256 startOffset
) private pure returns (bytes memory payload, uint256 offset) {
uint32 payloadLength;
(payloadLength, offset) = encoded.asUint32Unchecked(startOffset);
(payload, offset) = encoded.sliceUnchecked(offset, payloadLength);
}
function checkUint8(
bytes memory encoded,
uint256 startOffset,
uint8 expectedPayloadId
) private pure returns (uint256 offset) {
uint8 parsedPayloadId;
(parsedPayloadId, offset) = encoded.asUint8Unchecked(startOffset);
if (parsedPayloadId != expectedPayloadId) {
revert InvalidPayloadId(parsedPayloadId, expectedPayloadId);
}
}
function checkLength(bytes memory encoded, uint256 expected) private pure {
if (encoded.length != expected) {
revert InvalidPayloadLength(encoded.length, expected);
}
}
}