Files
tycho-execution/foundry/src/executors/BalancerV3Executor.sol
Diana Carvalho a82d0f2885 refactor: forge fmt (after foundry update)
Took 5 minutes


Took 8 seconds
2025-10-16 16:49:59 +01:00

132 lines
4.0 KiB
Solidity

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.26;
import "@interfaces/IExecutor.sol";
import {
IERC20,
SafeERC20
} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IVault} from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";
import {
SwapKind,
VaultSwapParams
} from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol";
import {RestrictTransferFrom} from "../RestrictTransferFrom.sol";
import {ICallback} from "@interfaces/ICallback.sol";
error BalancerV3Executor__InvalidDataLength();
error BalancerV3Executor__SenderIsNotVault(address sender);
contract BalancerV3Executor is IExecutor, RestrictTransferFrom, ICallback {
using SafeERC20 for IERC20;
IVault private constant VAULT =
IVault(0xbA1333333333a1BA1108E8412f11850A5C319bA9);
constructor(address _permit2) RestrictTransferFrom(_permit2) {}
// slither-disable-next-line locked-ether
function swap(uint256 givenAmount, bytes calldata data)
external
payable
returns (uint256 calculatedAmount)
{
if (data.length != 81) {
revert BalancerV3Executor__InvalidDataLength();
}
bytes memory result = VAULT.unlock(
abi.encodeCall(
BalancerV3Executor.swapCallback,
abi.encodePacked(givenAmount, data)
)
);
calculatedAmount = abi.decode(abi.decode(result, (bytes)), (uint256));
}
function verifyCallback(
bytes calldata /*data*/
)
public
view
{
if (msg.sender != address(VAULT)) {
revert BalancerV3Executor__SenderIsNotVault(msg.sender);
}
}
function _swapCallback(bytes calldata data)
internal
returns (bytes memory result)
{
verifyCallback(data);
(
uint256 amountGiven,
IERC20 tokenIn,
IERC20 tokenOut,
address poolId,
TransferType transferType,
address receiver
) = _decodeData(data);
uint256 amountCalculated;
uint256 amountIn;
uint256 amountOut;
(amountCalculated, amountIn, amountOut) = VAULT.swap(
VaultSwapParams({
kind: SwapKind.EXACT_IN,
pool: poolId,
tokenIn: tokenIn,
tokenOut: tokenOut,
amountGivenRaw: amountGiven,
limitRaw: 0,
userData: ""
})
);
_transfer(address(VAULT), transferType, address(tokenIn), amountIn);
// slither-disable-next-line unused-return
VAULT.settle(tokenIn, amountIn);
VAULT.sendTo(tokenOut, receiver, amountOut);
return abi.encode(amountCalculated);
}
function handleCallback(bytes calldata data)
external
returns (bytes memory result)
{
verifyCallback(data);
// Remove the first 68 bytes 4 selector + 32 dataOffset + 32 dataLength and extra padding at the end
result = _swapCallback(data[68:181]);
// Our general callback logic returns a not ABI encoded result (see Dispatcher._callHandleCallbackOnExecutor).
// However, the Vault expects the result to be ABI encoded. That is why we need to encode it here again.
return abi.encode(result);
}
function swapCallback(bytes calldata data)
external
returns (bytes memory result)
{
return _swapCallback(data);
}
function _decodeData(bytes calldata data)
internal
pure
returns (
uint256 amountGiven,
IERC20 tokenIn,
IERC20 tokenOut,
address poolId,
TransferType transferType,
address receiver
)
{
amountGiven = uint256(bytes32(data[0:32]));
tokenIn = IERC20(address(bytes20(data[32:52])));
tokenOut = IERC20(address(bytes20(data[52:72])));
poolId = address(bytes20(data[72:92]));
transferType = TransferType(uint8(data[92]));
receiver = address(bytes20(data[93:113]));
}
}