132 lines
4.0 KiB
Solidity
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]));
|
|
}
|
|
}
|