From 54b53773699bfc6de584915885d36a17b789d0d6 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Tue, 25 Aug 2026 11:44:00 +0200 Subject: [PATCH] feat(libraries): add byte slicing and multi-hop path encoding --- src/libraries/BytesLib.sol | 53 ++++++++++++++++++++++++++++++++++++++ src/libraries/Path.sol | 37 ++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 src/libraries/BytesLib.sol create mode 100644 src/libraries/Path.sol diff --git a/src/libraries/BytesLib.sol b/src/libraries/BytesLib.sol new file mode 100644 index 0000000..dd51d0f --- /dev/null +++ b/src/libraries/BytesLib.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.36; + +/// @title Slicing and word reads over tightly packed calldata paths. +library BytesLib { + function slice(bytes memory source, uint256 start, uint256 length) internal pure returns (bytes memory) { + require(length + 31 >= length, "BytesLib: SLICE_OVERFLOW"); + require(source.length >= start + length, "BytesLib: SLICE_OOB"); + + bytes memory result; + assembly { + switch iszero(length) + case 0 { + result := mload(0x40) + let lengthmod := and(length, 31) + let mc := add(add(result, lengthmod), mul(0x20, iszero(lengthmod))) + let end := add(mc, length) + for { + let cc := add(add(add(source, lengthmod), mul(0x20, iszero(lengthmod))), start) + } lt(mc, end) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { mstore(mc, mload(cc)) } + mstore(result, length) + mstore(0x40, and(add(mc, 31), not(31))) + } + default { + result := mload(0x40) + mstore(result, 0) + mstore(0x40, add(result, 0x20)) + } + } + return result; + } + + function toAddress(bytes memory source, uint256 start) internal pure returns (address) { + require(source.length >= start + 20, "BytesLib: ADDRESS_OOB"); + address result; + assembly { + result := div(mload(add(add(source, 0x20), start)), 0x1000000000000000000000000) + } + return result; + } + + function toUint24(bytes memory source, uint256 start) internal pure returns (uint24) { + require(source.length >= start + 3, "BytesLib: UINT24_OOB"); + uint24 result; + assembly { + result := mload(add(add(source, 0x3), start)) + } + return result; + } +} diff --git a/src/libraries/Path.sol b/src/libraries/Path.sol new file mode 100644 index 0000000..c43808f --- /dev/null +++ b/src/libraries/Path.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.36; + +import {BytesLib} from "./BytesLib.sol"; + +/// @title Encoding of multi-hop swap paths as packed token/fee/token sequences. +library Path { + using BytesLib for bytes; + + uint256 private constant addrSize = 20; + uint256 private constant feeSize = 3; + uint256 private constant nextOffset = addrSize + feeSize; + uint256 private constant popOffset = nextOffset + addrSize; + uint256 private constant multiplePoolsMinLength = popOffset + nextOffset; + + function hasMultiplePools(bytes memory path) internal pure returns (bool) { + return path.length >= multiplePoolsMinLength; + } + + function numPools(bytes memory path) internal pure returns (uint256) { + return (path.length - addrSize) / nextOffset; + } + + function decodeFirstPool(bytes memory path) internal pure returns (address tokenA, address tokenB, uint24 fee) { + tokenA = path.toAddress(0); + fee = path.toUint24(addrSize); + tokenB = path.toAddress(nextOffset); + } + + function getFirstPool(bytes memory path) internal pure returns (bytes memory) { + return path.slice(0, popOffset); + } + + function skipToken(bytes memory path) internal pure returns (bytes memory) { + return path.slice(nextOffset, path.length - nextOffset); + } +}