feat: Add transfer out for Uniswap V4
Add transfer in Executor and pass receiver address in encoding This is done by using the TAKE action instead of TAKE_ALL --- don't change below this line --- ENG-4315 Took 1 hour 36 minutes Took 2 minutes
This commit is contained in:
@@ -46,6 +46,7 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback, TokenTransfer {
|
|||||||
address tokenOut,
|
address tokenOut,
|
||||||
bool zeroForOne,
|
bool zeroForOne,
|
||||||
TransferType transferType,
|
TransferType transferType,
|
||||||
|
address receiver,
|
||||||
UniswapV4Executor.UniswapV4Pool[] memory pools
|
UniswapV4Executor.UniswapV4Pool[] memory pools
|
||||||
) = _decodeData(data);
|
) = _decodeData(data);
|
||||||
|
|
||||||
@@ -72,7 +73,7 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback, TokenTransfer {
|
|||||||
bytes memory actions = abi.encodePacked(
|
bytes memory actions = abi.encodePacked(
|
||||||
uint8(Actions.SWAP_EXACT_IN_SINGLE),
|
uint8(Actions.SWAP_EXACT_IN_SINGLE),
|
||||||
uint8(Actions.SETTLE_ALL),
|
uint8(Actions.SETTLE_ALL),
|
||||||
uint8(Actions.TAKE_ALL)
|
uint8(Actions.TAKE)
|
||||||
);
|
);
|
||||||
|
|
||||||
bytes[] memory params = new bytes[](3);
|
bytes[] memory params = new bytes[](3);
|
||||||
@@ -87,7 +88,7 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback, TokenTransfer {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
params[1] = abi.encode(tokenIn, amountIn); // currency to settle
|
params[1] = abi.encode(tokenIn, amountIn); // currency to settle
|
||||||
params[2] = abi.encode(tokenOut, uint256(0)); // currency to take
|
params[2] = abi.encode(tokenOut, receiver, uint256(0)); // currency to take. 0 means to take the full amount
|
||||||
swapData = abi.encode(actions, params);
|
swapData = abi.encode(actions, params);
|
||||||
} else {
|
} else {
|
||||||
PathKey[] memory path = new PathKey[](pools.length);
|
PathKey[] memory path = new PathKey[](pools.length);
|
||||||
@@ -104,7 +105,7 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback, TokenTransfer {
|
|||||||
bytes memory actions = abi.encodePacked(
|
bytes memory actions = abi.encodePacked(
|
||||||
uint8(Actions.SWAP_EXACT_IN),
|
uint8(Actions.SWAP_EXACT_IN),
|
||||||
uint8(Actions.SETTLE_ALL),
|
uint8(Actions.SETTLE_ALL),
|
||||||
uint8(Actions.TAKE_ALL)
|
uint8(Actions.TAKE)
|
||||||
);
|
);
|
||||||
|
|
||||||
bytes[] memory params = new bytes[](3);
|
bytes[] memory params = new bytes[](3);
|
||||||
@@ -119,22 +120,22 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback, TokenTransfer {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
params[1] = abi.encode(currencyIn, amountIn);
|
params[1] = abi.encode(currencyIn, amountIn);
|
||||||
params[2] = abi.encode(Currency.wrap(tokenOut), uint256(0));
|
params[2] = abi.encode(Currency.wrap(tokenOut), receiver, uint256(0));
|
||||||
swapData = abi.encode(actions, params);
|
swapData = abi.encode(actions, params);
|
||||||
}
|
}
|
||||||
uint256 tokenOutBalanceBefore;
|
uint256 tokenOutBalanceBefore;
|
||||||
|
|
||||||
tokenOutBalanceBefore = tokenOut == address(0)
|
tokenOutBalanceBefore = tokenOut == address(0)
|
||||||
? address(this).balance
|
? receiver.balance
|
||||||
: IERC20(tokenOut).balanceOf(address(this));
|
: IERC20(tokenOut).balanceOf(receiver);
|
||||||
|
|
||||||
executeActions(swapData);
|
executeActions(swapData);
|
||||||
|
|
||||||
uint256 tokenOutBalanceAfter;
|
uint256 tokenOutBalanceAfter;
|
||||||
|
|
||||||
tokenOutBalanceAfter = tokenOut == address(0)
|
tokenOutBalanceAfter = tokenOut == address(0)
|
||||||
? address(this).balance
|
? receiver.balance
|
||||||
: IERC20(tokenOut).balanceOf(address(this));
|
: IERC20(tokenOut).balanceOf(receiver);
|
||||||
|
|
||||||
calculatedAmount = tokenOutBalanceAfter - tokenOutBalanceBefore;
|
calculatedAmount = tokenOutBalanceAfter - tokenOutBalanceBefore;
|
||||||
|
|
||||||
@@ -155,10 +156,11 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback, TokenTransfer {
|
|||||||
address tokenOut,
|
address tokenOut,
|
||||||
bool zeroForOne,
|
bool zeroForOne,
|
||||||
TransferType transferType,
|
TransferType transferType,
|
||||||
|
address receiver,
|
||||||
UniswapV4Pool[] memory pools
|
UniswapV4Pool[] memory pools
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (data.length < 67) {
|
if (data.length < 88) {
|
||||||
revert UniswapV4Executor__InvalidDataLength();
|
revert UniswapV4Executor__InvalidDataLength();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,10 +168,11 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback, TokenTransfer {
|
|||||||
tokenOut = address(bytes20(data[20:40]));
|
tokenOut = address(bytes20(data[20:40]));
|
||||||
zeroForOne = (data[40] != 0);
|
zeroForOne = (data[40] != 0);
|
||||||
transferType = TransferType(uint8(data[41]));
|
transferType = TransferType(uint8(data[41]));
|
||||||
|
receiver = address(bytes20(data[42:62]));
|
||||||
|
|
||||||
uint256 poolsLength = (data.length - 42) / 26; // 26 bytes per pool object
|
uint256 poolsLength = (data.length - 62) / 26; // 26 bytes per pool object
|
||||||
pools = new UniswapV4Pool[](poolsLength);
|
pools = new UniswapV4Pool[](poolsLength);
|
||||||
bytes memory poolsData = data[42:];
|
bytes memory poolsData = data[62:];
|
||||||
uint256 offset = 0;
|
uint256 offset = 0;
|
||||||
for (uint256 i = 0; i < poolsLength; i++) {
|
for (uint256 i = 0; i < poolsLength; i++) {
|
||||||
address intermediaryToken;
|
address intermediaryToken;
|
||||||
|
|||||||
@@ -423,6 +423,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
|
|||||||
USDT_ADDR,
|
USDT_ADDR,
|
||||||
true,
|
true,
|
||||||
TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_ROUTER,
|
TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_ROUTER,
|
||||||
|
ALICE,
|
||||||
pools
|
pools
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -471,7 +472,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
|
|||||||
});
|
});
|
||||||
|
|
||||||
bytes memory protocolData = UniswapV4Utils.encodeExactInput(
|
bytes memory protocolData = UniswapV4Utils.encodeExactInput(
|
||||||
USDE_ADDR, WBTC_ADDR, true, TokenTransfer.TransferType.NONE, pools
|
USDE_ADDR, WBTC_ADDR, true, TokenTransfer.TransferType.NONE, ALICE, pools
|
||||||
);
|
);
|
||||||
|
|
||||||
bytes memory swap = encodeSplitSwap(
|
bytes memory swap = encodeSplitSwap(
|
||||||
@@ -483,7 +484,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
|
|||||||
|
|
||||||
tychoRouter.exposedSplitSwap(amountIn, 2, pleEncode(swaps));
|
tychoRouter.exposedSplitSwap(amountIn, 2, pleEncode(swaps));
|
||||||
|
|
||||||
assertEq(IERC20(WBTC_ADDR).balanceOf(tychoRouterAddr), 102718);
|
assertEq(IERC20(WBTC_ADDR).balanceOf(ALICE), 102718);
|
||||||
}
|
}
|
||||||
|
|
||||||
function testSplitInputCyclicSwapInternalMethod() public {
|
function testSplitInputCyclicSwapInternalMethod() public {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ contract UniswapV4ExecutorExposed is UniswapV4Executor {
|
|||||||
address tokenOut,
|
address tokenOut,
|
||||||
bool zeroForOne,
|
bool zeroForOne,
|
||||||
TokenTransfer.TransferType transferType,
|
TokenTransfer.TransferType transferType,
|
||||||
|
address receiver,
|
||||||
UniswapV4Pool[] memory pools
|
UniswapV4Pool[] memory pools
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
@@ -68,7 +69,7 @@ contract UniswapV4ExecutorTest is Test, Constants {
|
|||||||
});
|
});
|
||||||
|
|
||||||
bytes memory data = UniswapV4Utils.encodeExactInput(
|
bytes memory data = UniswapV4Utils.encodeExactInput(
|
||||||
USDE_ADDR, USDT_ADDR, zeroForOne, transferType, pools
|
USDE_ADDR, USDT_ADDR, zeroForOne, transferType, ALICE, pools
|
||||||
);
|
);
|
||||||
|
|
||||||
(
|
(
|
||||||
@@ -76,6 +77,7 @@ contract UniswapV4ExecutorTest is Test, Constants {
|
|||||||
address tokenOut,
|
address tokenOut,
|
||||||
bool zeroForOneDecoded,
|
bool zeroForOneDecoded,
|
||||||
TokenTransfer.TransferType transferTypeDecoded,
|
TokenTransfer.TransferType transferTypeDecoded,
|
||||||
|
address receiver,
|
||||||
UniswapV4Executor.UniswapV4Pool[] memory decodedPools
|
UniswapV4Executor.UniswapV4Pool[] memory decodedPools
|
||||||
) = uniswapV4Exposed.decodeData(data);
|
) = uniswapV4Exposed.decodeData(data);
|
||||||
|
|
||||||
@@ -83,6 +85,7 @@ contract UniswapV4ExecutorTest is Test, Constants {
|
|||||||
assertEq(tokenOut, USDT_ADDR);
|
assertEq(tokenOut, USDT_ADDR);
|
||||||
assertEq(zeroForOneDecoded, zeroForOne);
|
assertEq(zeroForOneDecoded, zeroForOne);
|
||||||
assertEq(uint8(transferTypeDecoded), uint8(transferType));
|
assertEq(uint8(transferTypeDecoded), uint8(transferType));
|
||||||
|
assertEq(receiver, ALICE);
|
||||||
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);
|
||||||
@@ -108,7 +111,7 @@ contract UniswapV4ExecutorTest is Test, Constants {
|
|||||||
});
|
});
|
||||||
|
|
||||||
bytes memory data = UniswapV4Utils.encodeExactInput(
|
bytes memory data = UniswapV4Utils.encodeExactInput(
|
||||||
USDE_ADDR, USDT_ADDR, true, TokenTransfer.TransferType.NONE, pools
|
USDE_ADDR, USDT_ADDR, true, TokenTransfer.TransferType.NONE, ALICE, pools
|
||||||
);
|
);
|
||||||
|
|
||||||
uint256 amountOut = uniswapV4Exposed.swap(amountIn, data);
|
uint256 amountOut = uniswapV4Exposed.swap(amountIn, data);
|
||||||
@@ -117,7 +120,7 @@ contract UniswapV4ExecutorTest is Test, Constants {
|
|||||||
USDE.balanceOf(address(uniswapV4Exposed)),
|
USDE.balanceOf(address(uniswapV4Exposed)),
|
||||||
usdeBalanceBeforeSwapExecutor - amountIn
|
usdeBalanceBeforeSwapExecutor - amountIn
|
||||||
);
|
);
|
||||||
assertTrue(USDT.balanceOf(address(uniswapV4Exposed)) == amountOut);
|
assertTrue(USDT.balanceOf(ALICE) == amountOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
function testSingleSwapIntegration() public {
|
function testSingleSwapIntegration() public {
|
||||||
@@ -134,10 +137,10 @@ contract UniswapV4ExecutorTest is Test, Constants {
|
|||||||
uint256 amountOut = uniswapV4Exposed.swap(amountIn, protocolData);
|
uint256 amountOut = uniswapV4Exposed.swap(amountIn, protocolData);
|
||||||
assertEq(USDE.balanceOf(poolManager), usdeBalanceBeforePool + amountIn);
|
assertEq(USDE.balanceOf(poolManager), usdeBalanceBeforePool + amountIn);
|
||||||
assertEq(
|
assertEq(
|
||||||
USDE.balanceOf(address(uniswapV4Exposed)),
|
USDE.balanceOf(ALICE),
|
||||||
usdeBalanceBeforeSwapExecutor - amountIn
|
usdeBalanceBeforeSwapExecutor - amountIn
|
||||||
);
|
);
|
||||||
assertTrue(USDT.balanceOf(address(uniswapV4Exposed)) == amountOut);
|
assertTrue(USDT.balanceOf(ALICE) == amountOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
function testMultipleSwap() public {
|
function testMultipleSwap() public {
|
||||||
@@ -162,7 +165,7 @@ contract UniswapV4ExecutorTest is Test, Constants {
|
|||||||
});
|
});
|
||||||
|
|
||||||
bytes memory data = UniswapV4Utils.encodeExactInput(
|
bytes memory data = UniswapV4Utils.encodeExactInput(
|
||||||
USDE_ADDR, WBTC_ADDR, true, TokenTransfer.TransferType.NONE, pools
|
USDE_ADDR, WBTC_ADDR, true, TokenTransfer.TransferType.NONE, ALICE, pools
|
||||||
);
|
);
|
||||||
|
|
||||||
uint256 amountOut = uniswapV4Exposed.swap(amountIn, data);
|
uint256 amountOut = uniswapV4Exposed.swap(amountIn, data);
|
||||||
@@ -172,7 +175,7 @@ contract UniswapV4ExecutorTest is Test, Constants {
|
|||||||
usdeBalanceBeforeSwapExecutor - amountIn
|
usdeBalanceBeforeSwapExecutor - amountIn
|
||||||
);
|
);
|
||||||
assertTrue(
|
assertTrue(
|
||||||
IERC20(WBTC_ADDR).balanceOf(address(uniswapV4Exposed)) == amountOut
|
IERC20(WBTC_ADDR).balanceOf(ALICE) == amountOut
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +199,7 @@ contract UniswapV4ExecutorTest is Test, Constants {
|
|||||||
usdeBalanceBeforeSwapExecutor - amountIn
|
usdeBalanceBeforeSwapExecutor - amountIn
|
||||||
);
|
);
|
||||||
assertTrue(
|
assertTrue(
|
||||||
IERC20(WBTC_ADDR).balanceOf(address(uniswapV4Exposed)) == amountOut
|
IERC20(WBTC_ADDR).balanceOf(ALICE) == amountOut
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ library UniswapV4Utils {
|
|||||||
address tokenOut,
|
address tokenOut,
|
||||||
bool zeroForOne,
|
bool zeroForOne,
|
||||||
UniswapV4Executor.TransferType transferType,
|
UniswapV4Executor.TransferType transferType,
|
||||||
|
address receiver,
|
||||||
UniswapV4Executor.UniswapV4Pool[] memory pools
|
UniswapV4Executor.UniswapV4Pool[] memory pools
|
||||||
) public pure returns (bytes memory) {
|
) public pure returns (bytes memory) {
|
||||||
bytes memory encodedPools;
|
bytes memory encodedPools;
|
||||||
@@ -23,7 +24,7 @@ library UniswapV4Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return abi.encodePacked(
|
return abi.encodePacked(
|
||||||
tokenIn, tokenOut, zeroForOne, transferType, encodedPools
|
tokenIn, tokenOut, zeroForOne, transferType, receiver, encodedPools
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1433,6 +1433,7 @@ mod tests {
|
|||||||
"6982508145454ce325ddbe47a25d4ec3d2311933", // group token in
|
"6982508145454ce325ddbe47a25d4ec3d2311933", // group token in
|
||||||
"00", // zero2one
|
"00", // zero2one
|
||||||
"04", // transfer type (transfer to router)
|
"04", // transfer type (transfer to router)
|
||||||
|
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
|
||||||
// First pool params
|
// First pool params
|
||||||
"0000000000000000000000000000000000000000", // intermediary token (ETH)
|
"0000000000000000000000000000000000000000", // intermediary token (ETH)
|
||||||
"000bb8", // fee
|
"000bb8", // fee
|
||||||
|
|||||||
@@ -207,6 +207,7 @@ impl SwapEncoder for UniswapV4SwapEncoder {
|
|||||||
group_token_out_address,
|
group_token_out_address,
|
||||||
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)?,
|
||||||
pool_params,
|
pool_params,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -766,9 +767,8 @@ mod tests {
|
|||||||
split: 0f64,
|
split: 0f64,
|
||||||
};
|
};
|
||||||
let encoding_context = EncodingContext {
|
let encoding_context = EncodingContext {
|
||||||
// The receiver address was taken from `address(uniswapV4Exposed)` in the
|
// The receiver is ALICE to match the solidity tests
|
||||||
// UniswapV4Executor.t.sol
|
receiver: Bytes::from("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2"),
|
||||||
receiver: Bytes::from("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f"),
|
|
||||||
exact_out: false,
|
exact_out: false,
|
||||||
// Same as the executor address
|
// Same as the executor address
|
||||||
router_address: Some(Bytes::from("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f")),
|
router_address: Some(Bytes::from("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f")),
|
||||||
@@ -800,6 +800,8 @@ mod tests {
|
|||||||
"01",
|
"01",
|
||||||
// transfer type
|
// transfer type
|
||||||
"00",
|
"00",
|
||||||
|
// receiver
|
||||||
|
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2",
|
||||||
// pool params:
|
// pool params:
|
||||||
// - intermediary token
|
// - intermediary token
|
||||||
"dac17f958d2ee523a2206206994597c13d831ec7",
|
"dac17f958d2ee523a2206206994597c13d831ec7",
|
||||||
@@ -878,11 +880,11 @@ mod tests {
|
|||||||
let usdt_address = Bytes::from("0xdAC17F958D2ee523a2206206994597C13D831ec7");
|
let usdt_address = Bytes::from("0xdAC17F958D2ee523a2206206994597C13D831ec7");
|
||||||
let wbtc_address = Bytes::from("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599");
|
let wbtc_address = Bytes::from("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599");
|
||||||
let router_address = Bytes::from("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f");
|
let router_address = Bytes::from("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f");
|
||||||
let receiver_address = router_address.clone();
|
|
||||||
|
|
||||||
// The context is the same for both swaps, since the group token in and out are the same
|
// The context is the same for both swaps, since the group token in and out are the same
|
||||||
let context = EncodingContext {
|
let context = EncodingContext {
|
||||||
receiver: receiver_address.clone(),
|
// The receiver is ALICE to match the solidity tests
|
||||||
|
receiver: Bytes::from("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2"),
|
||||||
exact_out: false,
|
exact_out: false,
|
||||||
router_address: Some(router_address.clone()),
|
router_address: Some(router_address.clone()),
|
||||||
group_token_in: usde_address.clone(),
|
group_token_in: usde_address.clone(),
|
||||||
@@ -968,6 +970,8 @@ mod tests {
|
|||||||
"01",
|
"01",
|
||||||
// transfer type
|
// transfer type
|
||||||
"00",
|
"00",
|
||||||
|
// receiver
|
||||||
|
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2",
|
||||||
// pool params:
|
// pool params:
|
||||||
// - intermediary token USDT
|
// - intermediary token USDT
|
||||||
"dac17f958d2ee523a2206206994597c13d831ec7",
|
"dac17f958d2ee523a2206206994597c13d831ec7",
|
||||||
@@ -983,6 +987,7 @@ mod tests {
|
|||||||
"00003c"
|
"00003c"
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
println!("{}", combined_hex)
|
||||||
}
|
}
|
||||||
|
|
||||||
mod ekubo {
|
mod ekubo {
|
||||||
|
|||||||
@@ -1050,6 +1050,8 @@ mod tests {
|
|||||||
"00",
|
"00",
|
||||||
// transfer type
|
// transfer type
|
||||||
"00",
|
"00",
|
||||||
|
// receiver
|
||||||
|
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2",
|
||||||
// first pool intermediary token (ETH)
|
// first pool intermediary token (ETH)
|
||||||
"0000000000000000000000000000000000000000",
|
"0000000000000000000000000000000000000000",
|
||||||
// fee
|
// fee
|
||||||
|
|||||||
Reference in New Issue
Block a user