feat: implement encoder and executor with single order support

This commit is contained in:
pedrobergamini
2025-06-04 17:57:09 -03:00
parent 8f8af59f24
commit 7e7e3f0e01
8 changed files with 922 additions and 15 deletions

View File

@@ -0,0 +1,284 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.26;
import "../../interfaces/IExecutor.sol";
import "../RestrictTransferFrom.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
import {
IERC20,
SafeERC20
} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/Address.sol";
/// @dev Bebop settlement interface for PMM RFQ swaps
interface IBebopSettlement {
struct Single {
uint256 expiry;
address taker_address;
address maker_address;
uint256 maker_nonce;
address taker_token;
address maker_token;
uint256 taker_amount;
uint256 maker_amount;
address receiver;
uint256 packed_commands;
uint256 flags;
}
struct Multi {
uint256 expiry;
address taker_address;
address maker_address;
uint256 maker_nonce;
address[] taker_tokens;
address[] maker_tokens;
uint256[] taker_amounts;
uint256[] maker_amounts;
address receiver;
uint256 packed_commands;
uint256 flags;
}
struct Aggregate {
uint256 expiry;
address taker_address;
uint256 taker_nonce;
address[] taker_tokens;
uint256[] taker_amounts;
address[] maker_addresses;
address[][] maker_tokens;
uint256[][] maker_amounts;
address receiver;
uint256 packed_commands;
uint256 flags;
}
struct MakerSignature {
uint8 signatureType;
bytes signatureBytes;
}
struct TakerSignature {
uint8 signatureType;
bytes signatureBytes;
}
/// @notice Executes a single RFQ order
function swapSingle(
Single calldata order,
MakerSignature calldata makerSignature,
uint256 filledTakerAmount
) external payable;
/// @notice Executes a multi-token RFQ order
function swapMulti(
Multi calldata order,
MakerSignature calldata makerSignature,
uint256[] calldata filledTakerAmounts
) external payable;
/// @notice Executes an aggregate RFQ order with multiple makers
function swapAggregate(
Aggregate calldata order,
MakerSignature[] calldata makerSignatures,
uint256[] calldata filledTakerAmounts
) external payable;
}
/// @title BebopExecutor
/// @notice Executor for Bebop PMM RFQ (Request for Quote) swaps
/// @dev Handles Single, Multi, and Aggregate RFQ swaps through Bebop settlement contract
contract BebopExecutor is IExecutor, IExecutorErrors, RestrictTransferFrom {
using Math for uint256;
using SafeERC20 for IERC20;
/// @notice Bebop order types
enum OrderType {
Single, // 0: Single token pair trade
Multi, // 1: Multi-token trade with single maker
Aggregate // 2: Multi-maker trade
}
/// @notice Bebop-specific errors
error BebopExecutor__SettlementFailed();
error BebopExecutor__UnsupportedOrderType(uint8 orderType);
error BebopExecutor__InvalidDataLength();
/// @notice The Bebop settlement contract address
address public immutable bebopSettlement;
/// @notice The native ETH address representation (same as Curve)
address public immutable nativeToken;
constructor(
address _bebopSettlement,
address _nativeToken,
address _permit2
) RestrictTransferFrom(_permit2) {
bebopSettlement = _bebopSettlement;
nativeToken = _nativeToken;
}
/// @notice Executes a swap through Bebop's PMM RFQ system
/// @param givenAmount The amount of input token to swap
/// @param data Encoded swap data containing tokens and quote information
/// @return calculatedAmount The amount of output token received
function swap(uint256 givenAmount, bytes calldata data)
external
payable
override
returns (uint256 calculatedAmount)
{
// Decode the packed data
(
address tokenIn,
address tokenOut,
TransferType transferType,
OrderType orderType,
bytes memory quoteData,
uint8 signatureType,
bytes memory signature,
bool approvalNeeded
) = _decodeData(data);
_transfer(address(this), transferType, tokenIn, givenAmount);
if (approvalNeeded) {
// slither-disable-next-line unused-return
IERC20(tokenIn).forceApprove(bebopSettlement, type(uint256).max);
}
// Execute RFQ swap based on order type
if (orderType == OrderType.Single) {
calculatedAmount = _executeSingleRFQ(
tokenIn,
tokenOut,
givenAmount,
quoteData,
signatureType,
signature
);
} else {
revert BebopExecutor__UnsupportedOrderType(uint8(orderType));
}
}
/// @dev Executes a Single RFQ swap through Bebop settlement
function _executeSingleRFQ(
address tokenIn,
address tokenOut,
uint256 amountIn,
bytes memory quoteData,
uint8 signatureType,
bytes memory signature
) private returns (uint256 amountOut) {
// Decode the order and signature from quoteData
(
IBebopSettlement.Single memory order,
IBebopSettlement.MakerSignature memory sig
) = _decodeQuoteData(quoteData, signatureType, signature);
// Record balances before swap to calculate amountOut
uint256 balanceBefore = tokenOut == nativeToken
? order.receiver.balance
: IERC20(tokenOut).balanceOf(order.receiver);
// Handle ETH vs ERC20 execution
if (tokenIn == nativeToken) {
// For ETH input, use msg.value
try IBebopSettlement(bebopSettlement).swapSingle{value: amountIn}(
order, sig, amountIn
) {
// Success, calculate amountOut from balance difference
} catch {
revert BebopExecutor__SettlementFailed();
}
} else {
// For ERC20 input, call settlement
try IBebopSettlement(bebopSettlement).swapSingle(
order, sig, amountIn
) {
// Success, calculate amountOut from balance difference
} catch {
revert BebopExecutor__SettlementFailed();
}
}
// Calculate actual amount received
uint256 balanceAfter = tokenOut == nativeToken
? order.receiver.balance
: IERC20(tokenOut).balanceOf(order.receiver);
amountOut = balanceAfter - balanceBefore;
}
/// @dev Decodes quote data into Bebop order and signature structures
function _decodeQuoteData(
bytes memory quoteData,
uint8 signatureType,
bytes memory signatureBytes
)
private
pure
returns (
IBebopSettlement.Single memory order,
IBebopSettlement.MakerSignature memory signature
)
{
// Decode the order from quoteData
order = abi.decode(quoteData, (IBebopSettlement.Single));
// Create signature struct with configurable type
signature = IBebopSettlement.MakerSignature({
signatureType: signatureType,
signatureBytes: signatureBytes
});
}
/// @dev Decodes the packed calldata
function _decodeData(bytes calldata data)
internal
pure
returns (
address tokenIn,
address tokenOut,
TransferType transferType,
OrderType orderType,
bytes memory quoteData,
uint8 signatureType,
bytes memory signature,
bool approvalNeeded
)
{
// Need at least 52 bytes for the fixed fields before we can read anything
if (data.length < 52) revert BebopExecutor__InvalidDataLength();
// Get the variable lengths so we know what to expect
uint32 quoteDataLength = uint32(bytes4(data[42:46]));
uint32 signatureLength = uint32(bytes4(data[47 + quoteDataLength:51 + quoteDataLength]));
// Make sure we got exactly what we expected, no more no less
uint256 expectedLength = 52 + quoteDataLength + signatureLength;
if (data.length != expectedLength) {
revert BebopExecutor__InvalidDataLength();
}
// All good, decode everything
tokenIn = address(bytes20(data[0:20]));
tokenOut = address(bytes20(data[20:40]));
transferType = TransferType(uint8(data[40]));
orderType = OrderType(uint8(data[41]));
// Quote data starts after the length field
quoteData = data[46:46 + quoteDataLength];
// Signature stuff comes after the quote data
signatureType = uint8(data[46 + quoteDataLength]);
signature = data[51 + quoteDataLength:51 + quoteDataLength + signatureLength];
// Last byte tells us if we need approval
approvalNeeded = data[51 + quoteDataLength + signatureLength] != 0;
}
}