Merge pull request #221 from propeller-heads/hooks/dc/ENG-4624-pass-hook-data
feat: Univ4 hooks
This commit is contained in:
@@ -5,7 +5,8 @@
|
|||||||
"pancakeswap_v2": "0xC9db3FEB380E4fd9af239e2595ECdEcE3b5c34A4",
|
"pancakeswap_v2": "0xC9db3FEB380E4fd9af239e2595ECdEcE3b5c34A4",
|
||||||
"uniswap_v3": "0xbab7124C9662B15C6b9AF0b1f329907dD55a24FC",
|
"uniswap_v3": "0xbab7124C9662B15C6b9AF0b1f329907dD55a24FC",
|
||||||
"pancakeswap_v3": "0x9D32e9F569B22Ae8d8C6f788037C1CD53632A059",
|
"pancakeswap_v3": "0x9D32e9F569B22Ae8d8C6f788037C1CD53632A059",
|
||||||
"uniswap_v4": "0xD11496EAb53A9521f0bC1e5c1098Ecb467103Ad9",
|
"uniswap_v4": "0x2C2EaB81Cf983602153E67b1890164BC4CABC6ed",
|
||||||
|
"uniswap_v4_hooks": "0x2C2EaB81Cf983602153E67b1890164BC4CABC6ed",
|
||||||
"vm:balancer_v2": "0xB5b8dc3F0a1Be99685a0DEd015Af93bFBB55C411",
|
"vm:balancer_v2": "0xB5b8dc3F0a1Be99685a0DEd015Af93bFBB55C411",
|
||||||
"ekubo_v2": "0x263DD7AD20983b5E0392bf1F09C4493500EDb333",
|
"ekubo_v2": "0x263DD7AD20983b5E0392bf1F09C4493500EDb333",
|
||||||
"vm:curve": "0x879F3008D96EBea0fc584aD684c7Df31777F3165",
|
"vm:curve": "0x879F3008D96EBea0fc584aD684c7Df31777F3165",
|
||||||
@@ -17,12 +18,14 @@
|
|||||||
"base": {
|
"base": {
|
||||||
"uniswap_v2": "0xF744EBfaA580cF3fFc25aD046E92BD8B770a0700",
|
"uniswap_v2": "0xF744EBfaA580cF3fFc25aD046E92BD8B770a0700",
|
||||||
"uniswap_v3": "0x647bffbf8bd72bf6341ecba8b0279e090313a40d",
|
"uniswap_v3": "0x647bffbf8bd72bf6341ecba8b0279e090313a40d",
|
||||||
"uniswap_v4": "0xb5628b97f491f85766673ac4d5d47bb1af7fcc4a",
|
"uniswap_v4": "0x7Dfa502736C7bd84DA1402F7524214215BC9534d",
|
||||||
|
"uniswap_v4_hooks": "0x7Dfa502736C7bd84DA1402F7524214215BC9534d",
|
||||||
"rfq:bebop": "0x489A3f531dA3873D6585BF3f8E0dEE48CAC6F7BC"
|
"rfq:bebop": "0x489A3f531dA3873D6585BF3f8E0dEE48CAC6F7BC"
|
||||||
},
|
},
|
||||||
"unichain": {
|
"unichain": {
|
||||||
"uniswap_v2": "0x00C1b81e3C8f6347E69e2DDb90454798A6Be975E",
|
"uniswap_v2": "0x00C1b81e3C8f6347E69e2DDb90454798A6Be975E",
|
||||||
"uniswap_v3": "0xD26A838A41af3d4815DfD745a080B2062c4124d1",
|
"uniswap_v3": "0xD26A838A41af3d4815DfD745a080B2062c4124d1",
|
||||||
"uniswap_v4": "0xF744EBfaA580cF3fFc25aD046E92BD8B770a0700"
|
"uniswap_v4": "0x647bfFbF8Bd72bF6341ECBa8B0279e090313A40D",
|
||||||
|
"uniswap_v4_hooks": "0x647bfFbF8Bd72bF6341ECBa8B0279e090313A40D"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"uniswap_v3": "0x2e234DAe75C793f67A35089C9d99245E1C58470b",
|
"uniswap_v3": "0x2e234DAe75C793f67A35089C9d99245E1C58470b",
|
||||||
"pancakeswap_v3": "0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9",
|
"pancakeswap_v3": "0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9",
|
||||||
"uniswap_v4": "0xF62849F9A0B5Bf2913b396098F7c7019b51A820a",
|
"uniswap_v4": "0xF62849F9A0B5Bf2913b396098F7c7019b51A820a",
|
||||||
|
"uniswap_v4_hooks": "0xF62849F9A0B5Bf2913b396098F7c7019b51A820a",
|
||||||
"vm:balancer_v2": "0xc7183455a4C133Ae270771860664b6B7ec320bB1",
|
"vm:balancer_v2": "0xc7183455a4C133Ae270771860664b6B7ec320bB1",
|
||||||
"ekubo_v2": "0xa0Cb889707d426A7A386870A03bc70d1b0697598",
|
"ekubo_v2": "0xa0Cb889707d426A7A386870A03bc70d1b0697598",
|
||||||
"vm:curve": "0x1d1499e622D69689cdf9004d05Ec547d650Ff211",
|
"vm:curve": "0x1d1499e622D69689cdf9004d05Ec547d650Ff211",
|
||||||
|
|||||||
@@ -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.
|
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.
|
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
|
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.
|
2. If it's not set, it will submit the transaction directly to the chain.
|
||||||
|
|
||||||
## Deploy Uniswap X filler
|
## Deploy Uniswap X filler
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {TransientStateLibrary} from
|
|||||||
"@uniswap/v4-core/src/libraries/TransientStateLibrary.sol";
|
"@uniswap/v4-core/src/libraries/TransientStateLibrary.sol";
|
||||||
import "../RestrictTransferFrom.sol";
|
import "../RestrictTransferFrom.sol";
|
||||||
import "@openzeppelin/contracts/utils/Address.sol";
|
import "@openzeppelin/contracts/utils/Address.sol";
|
||||||
|
import "../../lib/bytes/LibPrefixLengthEncodedByteArray.sol";
|
||||||
|
|
||||||
error UniswapV4Executor__InvalidDataLength();
|
error UniswapV4Executor__InvalidDataLength();
|
||||||
error UniswapV4Executor__NotPoolManager();
|
error UniswapV4Executor__NotPoolManager();
|
||||||
@@ -45,6 +46,7 @@ contract UniswapV4Executor is
|
|||||||
using CurrencyLibrary for Currency;
|
using CurrencyLibrary for Currency;
|
||||||
using V4SafeCast for *;
|
using V4SafeCast for *;
|
||||||
using TransientStateLibrary for IPoolManager;
|
using TransientStateLibrary for IPoolManager;
|
||||||
|
using LibPrefixLengthEncodedByteArray for bytes;
|
||||||
|
|
||||||
IPoolManager public immutable poolManager;
|
IPoolManager public immutable poolManager;
|
||||||
address private immutable _self;
|
address private immutable _self;
|
||||||
@@ -86,6 +88,8 @@ contract UniswapV4Executor is
|
|||||||
bool zeroForOne,
|
bool zeroForOne,
|
||||||
TransferType transferType,
|
TransferType transferType,
|
||||||
address receiver,
|
address receiver,
|
||||||
|
address hook,
|
||||||
|
bytes memory hookData,
|
||||||
UniswapV4Executor.UniswapV4Pool[] memory pools
|
UniswapV4Executor.UniswapV4Pool[] memory pools
|
||||||
) = _decodeData(data);
|
) = _decodeData(data);
|
||||||
bytes memory swapData;
|
bytes memory swapData;
|
||||||
@@ -95,7 +99,7 @@ contract UniswapV4Executor is
|
|||||||
currency1: Currency.wrap(zeroForOne ? tokenOut : tokenIn),
|
currency1: Currency.wrap(zeroForOne ? tokenOut : tokenIn),
|
||||||
fee: pools[0].fee,
|
fee: pools[0].fee,
|
||||||
tickSpacing: pools[0].tickSpacing,
|
tickSpacing: pools[0].tickSpacing,
|
||||||
hooks: IHooks(address(0))
|
hooks: IHooks(hook)
|
||||||
});
|
});
|
||||||
swapData = abi.encodeWithSelector(
|
swapData = abi.encodeWithSelector(
|
||||||
this.swapExactInputSingle.selector,
|
this.swapExactInputSingle.selector,
|
||||||
@@ -104,7 +108,7 @@ contract UniswapV4Executor is
|
|||||||
amountIn,
|
amountIn,
|
||||||
transferType,
|
transferType,
|
||||||
receiver,
|
receiver,
|
||||||
bytes("")
|
hookData
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
PathKey[] memory path = new PathKey[](pools.length);
|
PathKey[] memory path = new PathKey[](pools.length);
|
||||||
@@ -113,8 +117,8 @@ contract UniswapV4Executor is
|
|||||||
intermediateCurrency: Currency.wrap(pools[i].intermediaryToken),
|
intermediateCurrency: Currency.wrap(pools[i].intermediaryToken),
|
||||||
fee: pools[i].fee,
|
fee: pools[i].fee,
|
||||||
tickSpacing: pools[i].tickSpacing,
|
tickSpacing: pools[i].tickSpacing,
|
||||||
hooks: IHooks(address(0)),
|
hooks: IHooks(hook),
|
||||||
hookData: bytes("")
|
hookData: hookData
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,10 +148,12 @@ contract UniswapV4Executor is
|
|||||||
bool zeroForOne,
|
bool zeroForOne,
|
||||||
TransferType transferType,
|
TransferType transferType,
|
||||||
address receiver,
|
address receiver,
|
||||||
|
address hook,
|
||||||
|
bytes memory hookData,
|
||||||
UniswapV4Pool[] memory pools
|
UniswapV4Pool[] memory pools
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (data.length < 88) {
|
if (data.length < 108) {
|
||||||
revert UniswapV4Executor__InvalidDataLength();
|
revert UniswapV4Executor__InvalidDataLength();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,25 +162,42 @@ contract UniswapV4Executor is
|
|||||||
zeroForOne = data[40] != 0;
|
zeroForOne = data[40] != 0;
|
||||||
transferType = TransferType(uint8(data[41]));
|
transferType = TransferType(uint8(data[41]));
|
||||||
receiver = address(bytes20(data[42:62]));
|
receiver = address(bytes20(data[42:62]));
|
||||||
|
hook = address(bytes20(data[62:82]));
|
||||||
|
|
||||||
uint256 poolsLength = (data.length - 62) / 26; // 26 bytes per pool object
|
bytes calldata remaining = data[82:];
|
||||||
pools = new UniswapV4Pool[](poolsLength);
|
address firstToken = address(bytes20(remaining[0:20]));
|
||||||
bytes memory poolsData = data[62:];
|
uint24 firstFee = uint24(bytes3(remaining[20:23]));
|
||||||
uint256 offset = 0;
|
int24 firstTickSpacing = int24(uint24(bytes3(remaining[23:26])));
|
||||||
for (uint256 i = 0; i < poolsLength; i++) {
|
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;
|
address intermediaryToken;
|
||||||
uint24 fee;
|
uint24 fee;
|
||||||
int24 tickSpacing;
|
int24 tickSpacing;
|
||||||
|
|
||||||
// slither-disable-next-line assembly
|
// slither-disable-next-line assembly
|
||||||
assembly {
|
assembly {
|
||||||
intermediaryToken := mload(add(poolsData, add(offset, 20)))
|
intermediaryToken := mload(add(poolsData, add(0, 20)))
|
||||||
fee := shr(232, mload(add(poolsData, add(offset, 52))))
|
fee := shr(232, mload(add(poolsData, add(0, 52))))
|
||||||
tickSpacing := shr(232, mload(add(poolsData, add(offset, 55))))
|
tickSpacing := shr(232, mload(add(poolsData, add(0, 55))))
|
||||||
}
|
}
|
||||||
pools[i] = UniswapV4Pool(intermediaryToken, fee, tickSpacing);
|
pools[i + 1] = UniswapV4Pool(intermediaryToken, fee, tickSpacing);
|
||||||
offset += 26;
|
plePoolsTotalLength += 2 + encodedPoolsLength; // 2 bytes prefix + data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hookData = remaining[26 + plePoolsTotalLength:];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -24,6 +24,8 @@ contract UniswapV4ExecutorExposed is UniswapV4Executor {
|
|||||||
bool zeroForOne,
|
bool zeroForOne,
|
||||||
RestrictTransferFrom.TransferType transferType,
|
RestrictTransferFrom.TransferType transferType,
|
||||||
address receiver,
|
address receiver,
|
||||||
|
address hook,
|
||||||
|
bytes memory hookData,
|
||||||
UniswapV4Pool[] memory pools
|
UniswapV4Pool[] memory pools
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
@@ -37,10 +39,12 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
|
|||||||
UniswapV4ExecutorExposed uniswapV4Exposed;
|
UniswapV4ExecutorExposed uniswapV4Exposed;
|
||||||
IERC20 USDE = IERC20(USDE_ADDR);
|
IERC20 USDE = IERC20(USDE_ADDR);
|
||||||
IERC20 USDT = IERC20(USDT_ADDR);
|
IERC20 USDT = IERC20(USDT_ADDR);
|
||||||
|
IERC20 USDC = IERC20(USDC_ADDR);
|
||||||
|
|
||||||
address poolManager = 0x000000000004444c5dc75cB358380D2e3dE08A90;
|
address poolManager = 0x000000000004444c5dc75cB358380D2e3dE08A90;
|
||||||
|
|
||||||
function setUp() public {
|
function setUp() public {
|
||||||
uint256 forkBlock = 21817316;
|
uint256 forkBlock = 22689128;
|
||||||
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
|
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
|
||||||
uniswapV4Exposed = new UniswapV4ExecutorExposed(
|
uniswapV4Exposed = new UniswapV4ExecutorExposed(
|
||||||
IPoolManager(poolManager), PERMIT2_ADDRESS
|
IPoolManager(poolManager), PERMIT2_ADDRESS
|
||||||
@@ -73,6 +77,8 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
|
|||||||
zeroForOne,
|
zeroForOne,
|
||||||
RestrictTransferFrom.TransferType.Transfer,
|
RestrictTransferFrom.TransferType.Transfer,
|
||||||
ALICE,
|
ALICE,
|
||||||
|
address(0),
|
||||||
|
bytes(""),
|
||||||
pools
|
pools
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -82,6 +88,8 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
|
|||||||
bool zeroForOneDecoded,
|
bool zeroForOneDecoded,
|
||||||
RestrictTransferFrom.TransferType transferType,
|
RestrictTransferFrom.TransferType transferType,
|
||||||
address receiver,
|
address receiver,
|
||||||
|
address hook,
|
||||||
|
bytes memory hookData,
|
||||||
UniswapV4Executor.UniswapV4Pool[] memory decodedPools
|
UniswapV4Executor.UniswapV4Pool[] memory decodedPools
|
||||||
) = uniswapV4Exposed.decodeData(data);
|
) = uniswapV4Exposed.decodeData(data);
|
||||||
|
|
||||||
@@ -93,6 +101,7 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
|
|||||||
uint8(RestrictTransferFrom.TransferType.Transfer)
|
uint8(RestrictTransferFrom.TransferType.Transfer)
|
||||||
);
|
);
|
||||||
assertEq(receiver, ALICE);
|
assertEq(receiver, ALICE);
|
||||||
|
assertEq(hook, address(0));
|
||||||
assertEq(decodedPools.length, 2);
|
assertEq(decodedPools.length, 2);
|
||||||
assertEq(decodedPools[0].intermediaryToken, USDT_ADDR);
|
assertEq(decodedPools[0].intermediaryToken, USDT_ADDR);
|
||||||
assertEq(decodedPools[0].fee, pool1Fee);
|
assertEq(decodedPools[0].fee, pool1Fee);
|
||||||
@@ -123,6 +132,8 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
|
|||||||
true,
|
true,
|
||||||
RestrictTransferFrom.TransferType.Transfer,
|
RestrictTransferFrom.TransferType.Transfer,
|
||||||
ALICE,
|
ALICE,
|
||||||
|
address(0),
|
||||||
|
bytes(""),
|
||||||
pools
|
pools
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -180,6 +191,8 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
|
|||||||
true,
|
true,
|
||||||
RestrictTransferFrom.TransferType.Transfer,
|
RestrictTransferFrom.TransferType.Transfer,
|
||||||
ALICE,
|
ALICE,
|
||||||
|
address(0),
|
||||||
|
bytes(""),
|
||||||
pools
|
pools
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -211,6 +224,43 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
|
|||||||
);
|
);
|
||||||
assertTrue(IERC20(WBTC_ADDR).balanceOf(ALICE) == amountOut);
|
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 {
|
contract TychoRouterForBalancerV3Test is TychoRouterTestSetup {
|
||||||
@@ -237,6 +287,8 @@ contract TychoRouterForBalancerV3Test is TychoRouterTestSetup {
|
|||||||
true,
|
true,
|
||||||
RestrictTransferFrom.TransferType.TransferFrom,
|
RestrictTransferFrom.TransferType.TransferFrom,
|
||||||
ALICE,
|
ALICE,
|
||||||
|
address(0),
|
||||||
|
bytes(""),
|
||||||
pools
|
pools
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -285,6 +337,8 @@ contract TychoRouterForBalancerV3Test is TychoRouterTestSetup {
|
|||||||
true,
|
true,
|
||||||
RestrictTransferFrom.TransferType.TransferFrom,
|
RestrictTransferFrom.TransferType.TransferFrom,
|
||||||
ALICE,
|
ALICE,
|
||||||
|
address(0),
|
||||||
|
bytes(""),
|
||||||
pools
|
pools
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -10,13 +10,21 @@ library UniswapV4Utils {
|
|||||||
bool zeroForOne,
|
bool zeroForOne,
|
||||||
RestrictTransferFrom.TransferType transferType,
|
RestrictTransferFrom.TransferType transferType,
|
||||||
address receiver,
|
address receiver,
|
||||||
|
address hook,
|
||||||
|
bytes memory hookData,
|
||||||
UniswapV4Executor.UniswapV4Pool[] memory pools
|
UniswapV4Executor.UniswapV4Pool[] memory pools
|
||||||
) public pure returns (bytes memory) {
|
) 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++) {
|
bytes memory firstPool = abi.encodePacked(
|
||||||
encodedPools = abi.encodePacked(
|
pools[0].intermediaryToken,
|
||||||
encodedPools,
|
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,
|
pools[i].intermediaryToken,
|
||||||
bytes3(pools[i].fee),
|
bytes3(pools[i].fee),
|
||||||
pools[i].tickSpacing
|
pools[i].tickSpacing
|
||||||
@@ -24,7 +32,28 @@ library UniswapV4Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return abi.encodePacked(
|
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])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ pub const PROTOCOL_SPECIFIC_CONFIG: &str =
|
|||||||
pub static GROUPABLE_PROTOCOLS: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
|
pub static GROUPABLE_PROTOCOLS: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
|
||||||
let mut set = HashSet::new();
|
let mut set = HashSet::new();
|
||||||
set.insert("uniswap_v4");
|
set.insert("uniswap_v4");
|
||||||
|
set.insert("uniswap_v4_hooks");
|
||||||
set.insert("vm:balancer_v3");
|
set.insert("vm:balancer_v3");
|
||||||
set.insert("ekubo_v2");
|
set.insert("ekubo_v2");
|
||||||
set
|
set
|
||||||
@@ -29,6 +30,7 @@ pub static IN_TRANSFER_REQUIRED_PROTOCOLS: LazyLock<HashSet<&'static str>> = Laz
|
|||||||
set.insert("uniswap_v3");
|
set.insert("uniswap_v3");
|
||||||
set.insert("pancakeswap_v3");
|
set.insert("pancakeswap_v3");
|
||||||
set.insert("uniswap_v4");
|
set.insert("uniswap_v4");
|
||||||
|
set.insert("uniswap_v4_hooks");
|
||||||
set.insert("ekubo_v2");
|
set.insert("ekubo_v2");
|
||||||
set.insert("vm:maverick_v2");
|
set.insert("vm:maverick_v2");
|
||||||
set.insert("vm:balancer_v3");
|
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("uniswap_v3");
|
||||||
set.insert("pancakeswap_v3");
|
set.insert("pancakeswap_v3");
|
||||||
set.insert("uniswap_v4");
|
set.insert("uniswap_v4");
|
||||||
|
set.insert("uniswap_v4_hooks");
|
||||||
set.insert("ekubo_v2");
|
set.insert("ekubo_v2");
|
||||||
set.insert("vm:balancer_v3");
|
set.insert("vm:balancer_v3");
|
||||||
set
|
set
|
||||||
|
|||||||
@@ -121,16 +121,25 @@ impl StrategyEncoder for SingleSwapStrategyEncoder {
|
|||||||
transfer_type: transfer,
|
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() {
|
for swap in grouped_swap.swaps.iter() {
|
||||||
let protocol_data = swap_encoder.encode_swap(swap, &encoding_context)?;
|
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(
|
let swap_data = self.encode_swap_header(
|
||||||
Bytes::from_str(swap_encoder.executor_address())
|
Bytes::from_str(swap_encoder.executor_address())
|
||||||
.map_err(|_| EncodingError::FatalError("Invalid executor address".to_string()))?,
|
.map_err(|_| EncodingError::FatalError("Invalid executor address".to_string()))?,
|
||||||
grouped_protocol_data,
|
initial_protocol_data,
|
||||||
);
|
);
|
||||||
Ok(EncodedSolution {
|
Ok(EncodedSolution {
|
||||||
function_signature: self.function_signature.clone(),
|
function_signature: self.function_signature.clone(),
|
||||||
@@ -272,17 +281,26 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
|
|||||||
transfer_type: transfer,
|
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() {
|
for swap in grouped_swap.swaps.iter() {
|
||||||
let protocol_data = swap_encoder.encode_swap(swap, &encoding_context)?;
|
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(
|
let swap_data = self.encode_swap_header(
|
||||||
Bytes::from_str(swap_encoder.executor_address()).map_err(|_| {
|
Bytes::from_str(swap_encoder.executor_address()).map_err(|_| {
|
||||||
EncodingError::FatalError("Invalid executor address".to_string())
|
EncodingError::FatalError("Invalid executor address".to_string())
|
||||||
})?,
|
})?,
|
||||||
grouped_protocol_data,
|
initial_protocol_data,
|
||||||
);
|
);
|
||||||
swaps.push(swap_data);
|
swaps.push(swap_data);
|
||||||
}
|
}
|
||||||
@@ -463,10 +481,19 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
|
|||||||
transfer_type: transfer,
|
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() {
|
for swap in grouped_swap.swaps.iter() {
|
||||||
let protocol_data = swap_encoder.encode_swap(swap, &encoding_context)?;
|
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(
|
let swap_data = self.encode_swap_header(
|
||||||
@@ -476,7 +503,7 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
|
|||||||
Bytes::from_str(swap_encoder.executor_address()).map_err(|_| {
|
Bytes::from_str(swap_encoder.executor_address()).map_err(|_| {
|
||||||
EncodingError::FatalError("Invalid executor address".to_string())
|
EncodingError::FatalError("Invalid executor address".to_string())
|
||||||
})?,
|
})?,
|
||||||
grouped_protocol_data,
|
initial_protocol_data,
|
||||||
);
|
);
|
||||||
swaps.push(swap_data);
|
swaps.push(swap_data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,11 @@ impl SwapEncoderBuilder {
|
|||||||
self.chain,
|
self.chain,
|
||||||
self.config,
|
self.config,
|
||||||
)?)),
|
)?)),
|
||||||
|
"uniswap_v4_hooks" => Ok(Box::new(UniswapV4SwapEncoder::new(
|
||||||
|
self.executor_address,
|
||||||
|
self.chain,
|
||||||
|
self.config,
|
||||||
|
)?)),
|
||||||
"ekubo_v2" => {
|
"ekubo_v2" => {
|
||||||
Ok(Box::new(EkuboSwapEncoder::new(self.executor_address, self.chain, self.config)?))
|
Ok(Box::new(EkuboSwapEncoder::new(self.executor_address, self.chain, self.config)?))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,9 +185,28 @@ impl SwapEncoder for UniswapV4SwapEncoder {
|
|||||||
EncodingError::FatalError("Failed to pad tick spacing bytes".to_string())
|
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
|
// Early check if this is not the first swap
|
||||||
if encoding_context.group_token_in != swap.token_in {
|
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());
|
.abi_encode_packed());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +227,9 @@ impl SwapEncoder for UniswapV4SwapEncoder {
|
|||||||
zero_to_one,
|
zero_to_one,
|
||||||
(encoding_context.transfer_type as u8).to_be_bytes(),
|
(encoding_context.transfer_type as u8).to_be_bytes(),
|
||||||
bytes_to_address(&encoding_context.receiver)?,
|
bytes_to_address(&encoding_context.receiver)?,
|
||||||
|
hook_address,
|
||||||
pool_params,
|
pool_params,
|
||||||
|
hook_data,
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(args.abi_encode_packed())
|
Ok(args.abi_encode_packed())
|
||||||
@@ -1155,6 +1176,7 @@ mod tests {
|
|||||||
|
|
||||||
mod uniswap_v4 {
|
mod uniswap_v4 {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::encoding::evm::utils::{ple_encode, write_calldata_to_file};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_encode_uniswap_v4_simple_swap() {
|
fn test_encode_uniswap_v4_simple_swap() {
|
||||||
@@ -1210,6 +1232,8 @@ mod tests {
|
|||||||
"01",
|
"01",
|
||||||
// receiver
|
// receiver
|
||||||
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2",
|
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2",
|
||||||
|
// hook address (not set, so zero)
|
||||||
|
"0000000000000000000000000000000000000000",
|
||||||
// pool params:
|
// pool params:
|
||||||
// - intermediary token
|
// - intermediary token
|
||||||
"dac17f958d2ee523a2206206994597c13d831ec7",
|
"dac17f958d2ee523a2206206994597c13d831ec7",
|
||||||
@@ -1352,8 +1376,11 @@ mod tests {
|
|||||||
.encode_swap(&second_swap, &context)
|
.encode_swap(&second_swap, &context)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let combined_hex =
|
let combined_hex = format!(
|
||||||
format!("{}{}", encode(&initial_encoded_swap), encode(&second_encoded_swap));
|
"{}{}",
|
||||||
|
encode(&initial_encoded_swap),
|
||||||
|
encode(ple_encode(vec![second_encoded_swap]))
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
combined_hex,
|
combined_hex,
|
||||||
@@ -1368,6 +1395,8 @@ mod tests {
|
|||||||
"01",
|
"01",
|
||||||
// receiver
|
// receiver
|
||||||
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2",
|
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2",
|
||||||
|
// hook address (not set, so zero)
|
||||||
|
"0000000000000000000000000000000000000000",
|
||||||
// pool params:
|
// pool params:
|
||||||
// - intermediary token USDT
|
// - intermediary token USDT
|
||||||
"dac17f958d2ee523a2206206994597c13d831ec7",
|
"dac17f958d2ee523a2206206994597c13d831ec7",
|
||||||
@@ -1375,6 +1404,9 @@ mod tests {
|
|||||||
"000064",
|
"000064",
|
||||||
// - tick spacing
|
// - tick spacing
|
||||||
"000001",
|
"000001",
|
||||||
|
// Second swap
|
||||||
|
// ple encoding
|
||||||
|
"001a",
|
||||||
// - intermediary token WBTC
|
// - intermediary token WBTC
|
||||||
"2260fac5e5542a773aa44fbcfedf7c193bc2c599",
|
"2260fac5e5542a773aa44fbcfedf7c193bc2c599",
|
||||||
// - fee
|
// - fee
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use crate::encoding::{
|
|||||||
SequentialSwapStrategyEncoder, SingleSwapStrategyEncoder, SplitSwapStrategyEncoder,
|
SequentialSwapStrategyEncoder, SingleSwapStrategyEncoder, SplitSwapStrategyEncoder,
|
||||||
},
|
},
|
||||||
swap_encoder::swap_encoder_registry::SwapEncoderRegistry,
|
swap_encoder::swap_encoder_registry::SwapEncoderRegistry,
|
||||||
|
utils::ple_encode,
|
||||||
},
|
},
|
||||||
models::{
|
models::{
|
||||||
EncodedSolution, EncodingContext, NativeAction, Solution, Transaction, TransferType,
|
EncodedSolution, EncodingContext, NativeAction, Solution, Transaction, TransferType,
|
||||||
@@ -313,11 +314,12 @@ impl TychoExecutorEncoder {
|
|||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut grouped_protocol_data: Vec<u8> = vec![];
|
let transfer = if IN_TRANSFER_REQUIRED_PROTOCOLS.contains(
|
||||||
for swap in grouped_swap.swaps.iter() {
|
&grouped_swap.swaps[0]
|
||||||
let transfer = if IN_TRANSFER_REQUIRED_PROTOCOLS
|
.component
|
||||||
.contains(&swap.component.protocol_system.as_str())
|
.protocol_system
|
||||||
{
|
.as_str(),
|
||||||
|
) {
|
||||||
TransferType::Transfer
|
TransferType::Transfer
|
||||||
} else {
|
} else {
|
||||||
TransferType::None
|
TransferType::None
|
||||||
@@ -330,15 +332,26 @@ impl TychoExecutorEncoder {
|
|||||||
group_token_out: grouped_swap.token_out.clone(),
|
group_token_out: grouped_swap.token_out.clone(),
|
||||||
transfer_type: transfer,
|
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 protocol_data = swap_encoder.encode_swap(swap, &encoding_context)?;
|
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())
|
let executor_address = Bytes::from_str(swap_encoder.executor_address())
|
||||||
.map_err(|_| EncodingError::FatalError("Invalid executor address".to_string()))?;
|
.map_err(|_| EncodingError::FatalError("Invalid executor address".to_string()))?;
|
||||||
|
|
||||||
Ok(EncodedSolution {
|
Ok(EncodedSolution {
|
||||||
swaps: grouped_protocol_data,
|
swaps: initial_protocol_data,
|
||||||
interacting_with: executor_address,
|
interacting_with: executor_address,
|
||||||
permit: None,
|
permit: None,
|
||||||
function_signature: "".to_string(),
|
function_signature: "".to_string(),
|
||||||
@@ -1233,12 +1246,16 @@ mod tests {
|
|||||||
"01",
|
"01",
|
||||||
// receiver
|
// receiver
|
||||||
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2",
|
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2",
|
||||||
|
// hook address (not set, so zero)
|
||||||
|
"0000000000000000000000000000000000000000",
|
||||||
// first pool intermediary token (ETH)
|
// first pool intermediary token (ETH)
|
||||||
"0000000000000000000000000000000000000000",
|
"0000000000000000000000000000000000000000",
|
||||||
// fee
|
// fee
|
||||||
"000bb8",
|
"000bb8",
|
||||||
// tick spacing
|
// tick spacing
|
||||||
"00003c",
|
"00003c",
|
||||||
|
// ple encoding
|
||||||
|
"001a",
|
||||||
// second pool intermediary token (PEPE)
|
// second pool intermediary token (PEPE)
|
||||||
"6982508145454ce325ddbe47a25d4ec3d2311933",
|
"6982508145454ce325ddbe47a25d4ec3d2311933",
|
||||||
// fee
|
// fee
|
||||||
|
|||||||
@@ -373,7 +373,7 @@ fn test_single_encoding_strategy_usv4_grouped_swap() {
|
|||||||
|
|
||||||
let expected_swaps = String::from(concat!(
|
let expected_swaps = String::from(concat!(
|
||||||
// length of ple encoded swaps without padding
|
// length of ple encoded swaps without padding
|
||||||
"0000000000000000000000000000000000000000000000000000000000000086",
|
"000000000000000000000000000000000000000000000000000000000000009c",
|
||||||
// Swap data header
|
// Swap data header
|
||||||
"f62849f9a0b5bf2913b396098f7c7019b51a820a", // executor address
|
"f62849f9a0b5bf2913b396098f7c7019b51a820a", // executor address
|
||||||
// Protocol data
|
// Protocol data
|
||||||
@@ -382,15 +382,18 @@ fn test_single_encoding_strategy_usv4_grouped_swap() {
|
|||||||
"00", // zero2one
|
"00", // zero2one
|
||||||
"00", // transfer type TransferFrom
|
"00", // transfer type TransferFrom
|
||||||
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
|
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
|
||||||
|
"0000000000000000000000000000000000000000", // hook address
|
||||||
// First pool params
|
// First pool params
|
||||||
"0000000000000000000000000000000000000000", // intermediary token (ETH)
|
"0000000000000000000000000000000000000000", // intermediary token (ETH)
|
||||||
"000bb8", // fee
|
"000bb8", // fee
|
||||||
"00003c", // tick spacing
|
"00003c", // tick spacing
|
||||||
|
// ple encoding
|
||||||
|
"001a",
|
||||||
// Second pool params
|
// Second pool params
|
||||||
"6982508145454ce325ddbe47a25d4ec3d2311933", // intermediary token (PEPE)
|
"6982508145454ce325ddbe47a25d4ec3d2311933", // intermediary token (PEPE)
|
||||||
"0061a8", // fee
|
"0061a8", // fee
|
||||||
"0001f4", // tick spacing
|
"0001f4", // tick spacing
|
||||||
"0000000000000000000000000000000000000000000000000000" // padding
|
"00000000" // padding
|
||||||
));
|
));
|
||||||
|
|
||||||
let hex_calldata = encode(&calldata);
|
let hex_calldata = encode(&calldata);
|
||||||
|
|||||||
Reference in New Issue
Block a user