Merge pull request #221 from propeller-heads/hooks/dc/ENG-4624-pass-hook-data

feat: Univ4 hooks
This commit is contained in:
Tamara
2025-08-27 23:31:04 -04:00
committed by GitHub
13 changed files with 270 additions and 73 deletions

View File

@@ -5,7 +5,8 @@
"pancakeswap_v2": "0xC9db3FEB380E4fd9af239e2595ECdEcE3b5c34A4",
"uniswap_v3": "0xbab7124C9662B15C6b9AF0b1f329907dD55a24FC",
"pancakeswap_v3": "0x9D32e9F569B22Ae8d8C6f788037C1CD53632A059",
"uniswap_v4": "0xD11496EAb53A9521f0bC1e5c1098Ecb467103Ad9",
"uniswap_v4": "0x2C2EaB81Cf983602153E67b1890164BC4CABC6ed",
"uniswap_v4_hooks": "0x2C2EaB81Cf983602153E67b1890164BC4CABC6ed",
"vm:balancer_v2": "0xB5b8dc3F0a1Be99685a0DEd015Af93bFBB55C411",
"ekubo_v2": "0x263DD7AD20983b5E0392bf1F09C4493500EDb333",
"vm:curve": "0x879F3008D96EBea0fc584aD684c7Df31777F3165",
@@ -17,12 +18,14 @@
"base": {
"uniswap_v2": "0xF744EBfaA580cF3fFc25aD046E92BD8B770a0700",
"uniswap_v3": "0x647bffbf8bd72bf6341ecba8b0279e090313a40d",
"uniswap_v4": "0xb5628b97f491f85766673ac4d5d47bb1af7fcc4a",
"uniswap_v4": "0x7Dfa502736C7bd84DA1402F7524214215BC9534d",
"uniswap_v4_hooks": "0x7Dfa502736C7bd84DA1402F7524214215BC9534d",
"rfq:bebop": "0x489A3f531dA3873D6585BF3f8E0dEE48CAC6F7BC"
},
"unichain": {
"uniswap_v2": "0x00C1b81e3C8f6347E69e2DDb90454798A6Be975E",
"uniswap_v3": "0xD26A838A41af3d4815DfD745a080B2062c4124d1",
"uniswap_v4": "0xF744EBfaA580cF3fFc25aD046E92BD8B770a0700"
"uniswap_v4": "0x647bfFbF8Bd72bF6341ECBa8B0279e090313A40D",
"uniswap_v4_hooks": "0x647bfFbF8Bd72bF6341ECBa8B0279e090313A40D"
}
}

View File

@@ -6,6 +6,7 @@
"uniswap_v3": "0x2e234DAe75C793f67A35089C9d99245E1C58470b",
"pancakeswap_v3": "0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9",
"uniswap_v4": "0xF62849F9A0B5Bf2913b396098F7c7019b51A820a",
"uniswap_v4_hooks": "0xF62849F9A0B5Bf2913b396098F7c7019b51A820a",
"vm:balancer_v2": "0xc7183455a4C133Ae270771860664b6B7ec320bB1",
"ekubo_v2": "0xa0Cb889707d426A7A386870A03bc70d1b0697598",
"vm:curve": "0x1d1499e622D69689cdf9004d05Ec547d650Ff211",

View File

@@ -65,7 +65,7 @@ For each of the following, you must select one of `tenderly_ethereum`, `tenderly
1. If the wallet that has the role, is a Gnosis Safe, you need to set the `SAFE_ADDRESS` env var.
2. The scripts deploy-executors, remove-executor, set-roles and revoke-role all support this.
1. If `SAFE_ADDRESS` is set, then it will propose a transaction to the safe wallet and later on it needs to be
approved in their UI to execute on chain.
approved in their UI to execute on chain. Be sure to change the PRIVATE_KEY to that which has permissions on the safe wallet.
2. If it's not set, it will submit the transaction directly to the chain.
## Deploy Uniswap X filler

View File

@@ -25,6 +25,7 @@ import {TransientStateLibrary} from
"@uniswap/v4-core/src/libraries/TransientStateLibrary.sol";
import "../RestrictTransferFrom.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "../../lib/bytes/LibPrefixLengthEncodedByteArray.sol";
error UniswapV4Executor__InvalidDataLength();
error UniswapV4Executor__NotPoolManager();
@@ -45,6 +46,7 @@ contract UniswapV4Executor is
using CurrencyLibrary for Currency;
using V4SafeCast for *;
using TransientStateLibrary for IPoolManager;
using LibPrefixLengthEncodedByteArray for bytes;
IPoolManager public immutable poolManager;
address private immutable _self;
@@ -86,6 +88,8 @@ contract UniswapV4Executor is
bool zeroForOne,
TransferType transferType,
address receiver,
address hook,
bytes memory hookData,
UniswapV4Executor.UniswapV4Pool[] memory pools
) = _decodeData(data);
bytes memory swapData;
@@ -95,7 +99,7 @@ contract UniswapV4Executor is
currency1: Currency.wrap(zeroForOne ? tokenOut : tokenIn),
fee: pools[0].fee,
tickSpacing: pools[0].tickSpacing,
hooks: IHooks(address(0))
hooks: IHooks(hook)
});
swapData = abi.encodeWithSelector(
this.swapExactInputSingle.selector,
@@ -104,7 +108,7 @@ contract UniswapV4Executor is
amountIn,
transferType,
receiver,
bytes("")
hookData
);
} else {
PathKey[] memory path = new PathKey[](pools.length);
@@ -113,8 +117,8 @@ contract UniswapV4Executor is
intermediateCurrency: Currency.wrap(pools[i].intermediaryToken),
fee: pools[i].fee,
tickSpacing: pools[i].tickSpacing,
hooks: IHooks(address(0)),
hookData: bytes("")
hooks: IHooks(hook),
hookData: hookData
});
}
@@ -144,10 +148,12 @@ contract UniswapV4Executor is
bool zeroForOne,
TransferType transferType,
address receiver,
address hook,
bytes memory hookData,
UniswapV4Pool[] memory pools
)
{
if (data.length < 88) {
if (data.length < 108) {
revert UniswapV4Executor__InvalidDataLength();
}
@@ -156,25 +162,42 @@ contract UniswapV4Executor is
zeroForOne = data[40] != 0;
transferType = TransferType(uint8(data[41]));
receiver = address(bytes20(data[42:62]));
hook = address(bytes20(data[62:82]));
uint256 poolsLength = (data.length - 62) / 26; // 26 bytes per pool object
pools = new UniswapV4Pool[](poolsLength);
bytes memory poolsData = data[62:];
uint256 offset = 0;
for (uint256 i = 0; i < poolsLength; i++) {
bytes calldata remaining = data[82:];
address firstToken = address(bytes20(remaining[0:20]));
uint24 firstFee = uint24(bytes3(remaining[20:23]));
int24 firstTickSpacing = int24(uint24(bytes3(remaining[23:26])));
UniswapV4Pool memory firstPool =
UniswapV4Pool(firstToken, firstFee, firstTickSpacing);
// Remaining after first pool are ple encoded
bytes[] memory encodedPools =
LibPrefixLengthEncodedByteArray.toArray(remaining[26:]);
pools = new UniswapV4Pool[](1 + encodedPools.length);
pools[0] = firstPool;
uint256 encodedPoolsLength = 26;
uint256 plePoolsTotalLength;
for (uint256 i = 0; i < encodedPools.length; i++) {
bytes memory poolsData = encodedPools[i];
address intermediaryToken;
uint24 fee;
int24 tickSpacing;
// slither-disable-next-line assembly
assembly {
intermediaryToken := mload(add(poolsData, add(offset, 20)))
fee := shr(232, mload(add(poolsData, add(offset, 52))))
tickSpacing := shr(232, mload(add(poolsData, add(offset, 55))))
intermediaryToken := mload(add(poolsData, add(0, 20)))
fee := shr(232, mload(add(poolsData, add(0, 52))))
tickSpacing := shr(232, mload(add(poolsData, add(0, 55))))
}
pools[i] = UniswapV4Pool(intermediaryToken, fee, tickSpacing);
offset += 26;
pools[i + 1] = UniswapV4Pool(intermediaryToken, fee, tickSpacing);
plePoolsTotalLength += 2 + encodedPoolsLength; // 2 bytes prefix + data
}
hookData = remaining[26 + plePoolsTotalLength:];
}
/**

File diff suppressed because one or more lines are too long

View File

@@ -24,6 +24,8 @@ contract UniswapV4ExecutorExposed is UniswapV4Executor {
bool zeroForOne,
RestrictTransferFrom.TransferType transferType,
address receiver,
address hook,
bytes memory hookData,
UniswapV4Pool[] memory pools
)
{
@@ -37,10 +39,12 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
UniswapV4ExecutorExposed uniswapV4Exposed;
IERC20 USDE = IERC20(USDE_ADDR);
IERC20 USDT = IERC20(USDT_ADDR);
IERC20 USDC = IERC20(USDC_ADDR);
address poolManager = 0x000000000004444c5dc75cB358380D2e3dE08A90;
function setUp() public {
uint256 forkBlock = 21817316;
uint256 forkBlock = 22689128;
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
uniswapV4Exposed = new UniswapV4ExecutorExposed(
IPoolManager(poolManager), PERMIT2_ADDRESS
@@ -73,6 +77,8 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
zeroForOne,
RestrictTransferFrom.TransferType.Transfer,
ALICE,
address(0),
bytes(""),
pools
);
@@ -82,6 +88,8 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
bool zeroForOneDecoded,
RestrictTransferFrom.TransferType transferType,
address receiver,
address hook,
bytes memory hookData,
UniswapV4Executor.UniswapV4Pool[] memory decodedPools
) = uniswapV4Exposed.decodeData(data);
@@ -93,6 +101,7 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
uint8(RestrictTransferFrom.TransferType.Transfer)
);
assertEq(receiver, ALICE);
assertEq(hook, address(0));
assertEq(decodedPools.length, 2);
assertEq(decodedPools[0].intermediaryToken, USDT_ADDR);
assertEq(decodedPools[0].fee, pool1Fee);
@@ -123,6 +132,8 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
true,
RestrictTransferFrom.TransferType.Transfer,
ALICE,
address(0),
bytes(""),
pools
);
@@ -180,6 +191,8 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
true,
RestrictTransferFrom.TransferType.Transfer,
ALICE,
address(0),
bytes(""),
pools
);
@@ -211,6 +224,43 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
);
assertTrue(IERC20(WBTC_ADDR).balanceOf(ALICE) == amountOut);
}
function testSingleSwapEulerHook() public {
// Replicating tx: 0xb372306a81c6e840f4ec55f006da6b0b097f435802a2e6fd216998dd12fb4aca
address hook = address(0x69058613588536167BA0AA94F0CC1Fe420eF28a8);
uint256 amountIn = 7407000000;
deal(USDC_ADDR, address(uniswapV4Exposed), amountIn);
uint256 usdcBalanceBeforeSwapExecutor =
USDC.balanceOf(address(uniswapV4Exposed));
UniswapV4Executor.UniswapV4Pool[] memory pools =
new UniswapV4Executor.UniswapV4Pool[](1);
pools[0] = UniswapV4Executor.UniswapV4Pool({
intermediaryToken: WETH_ADDR,
fee: uint24(500),
tickSpacing: int24(1)
});
bytes memory data = UniswapV4Utils.encodeExactInput(
USDC_ADDR,
WETH_ADDR,
true,
RestrictTransferFrom.TransferType.Transfer,
ALICE,
hook,
bytes(""),
pools
);
uint256 amountOut = uniswapV4Exposed.swap(amountIn, data);
assertEq(amountOut, 2681115183499232721);
assertEq(
USDC.balanceOf(address(uniswapV4Exposed)),
usdcBalanceBeforeSwapExecutor - amountIn
);
assertTrue(IERC20(WETH_ADDR).balanceOf(ALICE) == amountOut);
}
}
contract TychoRouterForBalancerV3Test is TychoRouterTestSetup {
@@ -237,6 +287,8 @@ contract TychoRouterForBalancerV3Test is TychoRouterTestSetup {
true,
RestrictTransferFrom.TransferType.TransferFrom,
ALICE,
address(0),
bytes(""),
pools
);
@@ -285,6 +337,8 @@ contract TychoRouterForBalancerV3Test is TychoRouterTestSetup {
true,
RestrictTransferFrom.TransferType.TransferFrom,
ALICE,
address(0),
bytes(""),
pools
);

View File

@@ -10,13 +10,21 @@ library UniswapV4Utils {
bool zeroForOne,
RestrictTransferFrom.TransferType transferType,
address receiver,
address hook,
bytes memory hookData,
UniswapV4Executor.UniswapV4Pool[] memory pools
) public pure returns (bytes memory) {
bytes memory encodedPools;
require(pools.length > 0, "Must have at least one pool");
for (uint256 i = 0; i < pools.length; i++) {
encodedPools = abi.encodePacked(
encodedPools,
bytes memory firstPool = abi.encodePacked(
pools[0].intermediaryToken,
bytes3(pools[0].fee),
pools[0].tickSpacing
);
bytes[] memory encodedExtraPools = new bytes[](pools.length - 1);
for (uint256 i = 1; i < pools.length; i++) {
encodedExtraPools[i - 1] = abi.encodePacked(
pools[i].intermediaryToken,
bytes3(pools[i].fee),
pools[i].tickSpacing
@@ -24,7 +32,28 @@ library UniswapV4Utils {
}
return abi.encodePacked(
tokenIn, tokenOut, zeroForOne, transferType, receiver, encodedPools
tokenIn,
tokenOut,
zeroForOne,
transferType,
receiver,
hook,
firstPool,
pleEncode(encodedExtraPools),
hookData
);
}
function pleEncode(bytes[] memory data)
public
pure
returns (bytes memory encoded)
{
for (uint256 i = 0; i < data.length; i++) {
encoded = bytes.concat(
encoded,
abi.encodePacked(bytes2(uint16(data[i].length)), data[i])
);
}
}
}

View File

@@ -13,6 +13,7 @@ pub const PROTOCOL_SPECIFIC_CONFIG: &str =
pub static GROUPABLE_PROTOCOLS: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
let mut set = HashSet::new();
set.insert("uniswap_v4");
set.insert("uniswap_v4_hooks");
set.insert("vm:balancer_v3");
set.insert("ekubo_v2");
set
@@ -29,6 +30,7 @@ pub static IN_TRANSFER_REQUIRED_PROTOCOLS: LazyLock<HashSet<&'static str>> = Laz
set.insert("uniswap_v3");
set.insert("pancakeswap_v3");
set.insert("uniswap_v4");
set.insert("uniswap_v4_hooks");
set.insert("ekubo_v2");
set.insert("vm:maverick_v2");
set.insert("vm:balancer_v3");
@@ -46,6 +48,7 @@ pub static CALLBACK_CONSTRAINED_PROTOCOLS: LazyLock<HashSet<&'static str>> = Laz
set.insert("uniswap_v3");
set.insert("pancakeswap_v3");
set.insert("uniswap_v4");
set.insert("uniswap_v4_hooks");
set.insert("ekubo_v2");
set.insert("vm:balancer_v3");
set

View File

@@ -121,16 +121,25 @@ impl StrategyEncoder for SingleSwapStrategyEncoder {
transfer_type: transfer,
};
let mut grouped_protocol_data: Vec<u8> = vec![];
let mut grouped_protocol_data: Vec<Vec<u8>> = vec![];
let mut initial_protocol_data: Vec<u8> = vec![];
for swap in grouped_swap.swaps.iter() {
let protocol_data = swap_encoder.encode_swap(swap, &encoding_context)?;
grouped_protocol_data.extend(protocol_data);
if encoding_context.group_token_in == swap.token_in {
initial_protocol_data = protocol_data;
} else {
grouped_protocol_data.push(protocol_data);
}
}
if !grouped_protocol_data.is_empty() {
initial_protocol_data.extend(ple_encode(grouped_protocol_data));
}
let swap_data = self.encode_swap_header(
Bytes::from_str(swap_encoder.executor_address())
.map_err(|_| EncodingError::FatalError("Invalid executor address".to_string()))?,
grouped_protocol_data,
initial_protocol_data,
);
Ok(EncodedSolution {
function_signature: self.function_signature.clone(),
@@ -272,17 +281,26 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
transfer_type: transfer,
};
let mut grouped_protocol_data: Vec<u8> = vec![];
let mut grouped_protocol_data: Vec<Vec<u8>> = vec![];
let mut initial_protocol_data: Vec<u8> = vec![];
for swap in grouped_swap.swaps.iter() {
let protocol_data = swap_encoder.encode_swap(swap, &encoding_context)?;
grouped_protocol_data.extend(protocol_data);
if encoding_context.group_token_in == swap.token_in {
initial_protocol_data = protocol_data;
} else {
grouped_protocol_data.push(protocol_data);
}
}
if !grouped_protocol_data.is_empty() {
initial_protocol_data.extend(ple_encode(grouped_protocol_data));
}
let swap_data = self.encode_swap_header(
Bytes::from_str(swap_encoder.executor_address()).map_err(|_| {
EncodingError::FatalError("Invalid executor address".to_string())
})?,
grouped_protocol_data,
initial_protocol_data,
);
swaps.push(swap_data);
}
@@ -463,10 +481,19 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
transfer_type: transfer,
};
let mut grouped_protocol_data: Vec<u8> = vec![];
let mut grouped_protocol_data: Vec<Vec<u8>> = vec![];
let mut initial_protocol_data: Vec<u8> = vec![];
for swap in grouped_swap.swaps.iter() {
let protocol_data = swap_encoder.encode_swap(swap, &encoding_context)?;
grouped_protocol_data.extend(protocol_data);
if encoding_context.group_token_in == swap.token_in {
initial_protocol_data = protocol_data;
} else {
grouped_protocol_data.push(protocol_data);
}
}
if !grouped_protocol_data.is_empty() {
initial_protocol_data.extend(ple_encode(grouped_protocol_data));
}
let swap_data = self.encode_swap_header(
@@ -476,7 +503,7 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
Bytes::from_str(swap_encoder.executor_address()).map_err(|_| {
EncodingError::FatalError("Invalid executor address".to_string())
})?,
grouped_protocol_data,
initial_protocol_data,
);
swaps.push(swap_data);
}

View File

@@ -72,6 +72,11 @@ impl SwapEncoderBuilder {
self.chain,
self.config,
)?)),
"uniswap_v4_hooks" => Ok(Box::new(UniswapV4SwapEncoder::new(
self.executor_address,
self.chain,
self.config,
)?)),
"ekubo_v2" => {
Ok(Box::new(EkuboSwapEncoder::new(self.executor_address, self.chain, self.config)?))
}

View File

@@ -185,9 +185,28 @@ impl SwapEncoder for UniswapV4SwapEncoder {
EncodingError::FatalError("Failed to pad tick spacing bytes".to_string())
})?;
let hook_address = match get_static_attribute(swap, "hooks") {
Ok(hook) => Address::from_slice(&hook),
Err(_) => Address::ZERO,
};
let mut hook_data = AlloyBytes::new();
if encoding_context.group_token_out == swap.token_out {
// Add hook data if it's only the last swap
hook_data = AlloyBytes::from(
swap.user_data
.clone()
.unwrap_or_default()
.to_vec(),
);
}
// Early check if this is not the first swap
if encoding_context.group_token_in != swap.token_in {
return Ok((bytes_to_address(&swap.token_out)?, pool_fee_u24, pool_tick_spacing_u24)
return Ok((
bytes_to_address(&swap.token_out)?,
pool_fee_u24,
pool_tick_spacing_u24,
hook_data,
)
.abi_encode_packed());
}
@@ -208,7 +227,9 @@ impl SwapEncoder for UniswapV4SwapEncoder {
zero_to_one,
(encoding_context.transfer_type as u8).to_be_bytes(),
bytes_to_address(&encoding_context.receiver)?,
hook_address,
pool_params,
hook_data,
);
Ok(args.abi_encode_packed())
@@ -1155,6 +1176,7 @@ mod tests {
mod uniswap_v4 {
use super::*;
use crate::encoding::evm::utils::{ple_encode, write_calldata_to_file};
#[test]
fn test_encode_uniswap_v4_simple_swap() {
@@ -1210,6 +1232,8 @@ mod tests {
"01",
// receiver
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2",
// hook address (not set, so zero)
"0000000000000000000000000000000000000000",
// pool params:
// - intermediary token
"dac17f958d2ee523a2206206994597c13d831ec7",
@@ -1352,8 +1376,11 @@ mod tests {
.encode_swap(&second_swap, &context)
.unwrap();
let combined_hex =
format!("{}{}", encode(&initial_encoded_swap), encode(&second_encoded_swap));
let combined_hex = format!(
"{}{}",
encode(&initial_encoded_swap),
encode(ple_encode(vec![second_encoded_swap]))
);
assert_eq!(
combined_hex,
@@ -1368,6 +1395,8 @@ mod tests {
"01",
// receiver
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2",
// hook address (not set, so zero)
"0000000000000000000000000000000000000000",
// pool params:
// - intermediary token USDT
"dac17f958d2ee523a2206206994597c13d831ec7",
@@ -1375,6 +1404,9 @@ mod tests {
"000064",
// - tick spacing
"000001",
// Second swap
// ple encoding
"001a",
// - intermediary token WBTC
"2260fac5e5542a773aa44fbcfedf7c193bc2c599",
// - fee

View File

@@ -14,6 +14,7 @@ use crate::encoding::{
SequentialSwapStrategyEncoder, SingleSwapStrategyEncoder, SplitSwapStrategyEncoder,
},
swap_encoder::swap_encoder_registry::SwapEncoderRegistry,
utils::ple_encode,
},
models::{
EncodedSolution, EncodingContext, NativeAction, Solution, Transaction, TransferType,
@@ -313,32 +314,44 @@ impl TychoExecutorEncoder {
))
})?;
let mut grouped_protocol_data: Vec<u8> = vec![];
let transfer = if IN_TRANSFER_REQUIRED_PROTOCOLS.contains(
&grouped_swap.swaps[0]
.component
.protocol_system
.as_str(),
) {
TransferType::Transfer
} else {
TransferType::None
};
let encoding_context = EncodingContext {
receiver: solution.receiver.clone(),
exact_out: solution.exact_out,
router_address: None,
group_token_in: grouped_swap.token_in.clone(),
group_token_out: grouped_swap.token_out.clone(),
transfer_type: transfer,
};
let mut grouped_protocol_data: Vec<Vec<u8>> = vec![];
let mut initial_protocol_data: Vec<u8> = vec![];
for swap in grouped_swap.swaps.iter() {
let transfer = if IN_TRANSFER_REQUIRED_PROTOCOLS
.contains(&swap.component.protocol_system.as_str())
{
TransferType::Transfer
} else {
TransferType::None
};
let encoding_context = EncodingContext {
receiver: solution.receiver.clone(),
exact_out: solution.exact_out,
router_address: None,
group_token_in: grouped_swap.token_in.clone(),
group_token_out: grouped_swap.token_out.clone(),
transfer_type: transfer,
};
let protocol_data = swap_encoder.encode_swap(swap, &encoding_context)?;
grouped_protocol_data.extend(protocol_data);
if encoding_context.group_token_in == swap.token_in {
initial_protocol_data = protocol_data;
} else {
grouped_protocol_data.push(protocol_data);
}
}
if !grouped_protocol_data.is_empty() {
initial_protocol_data.extend(ple_encode(grouped_protocol_data));
}
let executor_address = Bytes::from_str(swap_encoder.executor_address())
.map_err(|_| EncodingError::FatalError("Invalid executor address".to_string()))?;
Ok(EncodedSolution {
swaps: grouped_protocol_data,
swaps: initial_protocol_data,
interacting_with: executor_address,
permit: None,
function_signature: "".to_string(),
@@ -1233,12 +1246,16 @@ mod tests {
"01",
// receiver
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2",
// hook address (not set, so zero)
"0000000000000000000000000000000000000000",
// first pool intermediary token (ETH)
"0000000000000000000000000000000000000000",
// fee
"000bb8",
// tick spacing
"00003c",
// ple encoding
"001a",
// second pool intermediary token (PEPE)
"6982508145454ce325ddbe47a25d4ec3d2311933",
// fee

View File

@@ -373,7 +373,7 @@ fn test_single_encoding_strategy_usv4_grouped_swap() {
let expected_swaps = String::from(concat!(
// length of ple encoded swaps without padding
"0000000000000000000000000000000000000000000000000000000000000086",
"000000000000000000000000000000000000000000000000000000000000009c",
// Swap data header
"f62849f9a0b5bf2913b396098f7c7019b51a820a", // executor address
// Protocol data
@@ -382,15 +382,18 @@ fn test_single_encoding_strategy_usv4_grouped_swap() {
"00", // zero2one
"00", // transfer type TransferFrom
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
"0000000000000000000000000000000000000000", // hook address
// First pool params
"0000000000000000000000000000000000000000", // intermediary token (ETH)
"000bb8", // fee
"00003c", // tick spacing
// ple encoding
"001a",
// Second pool params
"6982508145454ce325ddbe47a25d4ec3d2311933", // intermediary token (PEPE)
"0061a8", // fee
"0001f4", // tick spacing
"0000000000000000000000000000000000000000000000000000" // padding
"00000000" // padding
));
let hex_calldata = encode(&calldata);