148 lines
5.8 KiB
Solidity
148 lines
5.8 KiB
Solidity
// SPDX-License-Identifier: BUSL-1.1
|
|
pragma solidity ^0.8.26;
|
|
|
|
import "@src/executors/BebopExecutor.sol";
|
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
|
|
/**
|
|
* @title BebopSettlementMock
|
|
* @notice Mock Bebop settlement contract that skips taker_address validation
|
|
* @dev This is used for testing purposes to work around the msg.sender == taker_address check
|
|
* while maintaining all other Bebop settlement logic
|
|
*/
|
|
contract BebopSettlementMock {
|
|
error InvalidSignature();
|
|
error OrderExpired();
|
|
error InsufficientMakerBalance();
|
|
|
|
// Nonce tracking to prevent replay attacks
|
|
mapping(address => mapping(uint256 => bool)) public makerNonceUsed;
|
|
|
|
function swapSingle(
|
|
IBebopSettlement.Single calldata order,
|
|
IBebopSettlement.MakerSignature calldata makerSignature,
|
|
uint256 filledTakerAmount
|
|
) external payable {
|
|
// Check order expiry
|
|
if (block.timestamp > order.expiry) revert OrderExpired();
|
|
|
|
// Check nonce hasn't been used
|
|
if (makerNonceUsed[order.maker_address][order.maker_nonce]) {
|
|
revert InvalidSignature();
|
|
}
|
|
|
|
// IMPORTANT: We skip the taker_address validation that would normally be here:
|
|
// if (msg.sender != order.taker_address) revert InvalidCaller();
|
|
|
|
// For testing, we'll do a simplified signature validation
|
|
// In reality, Bebop would validate the full order signature
|
|
// Accept both proper 65-byte signatures and test placeholders
|
|
if (makerSignature.signatureBytes.length < 4) {
|
|
revert InvalidSignature();
|
|
}
|
|
|
|
// Mark nonce as used
|
|
makerNonceUsed[order.maker_address][order.maker_nonce] = true;
|
|
|
|
// Calculate amounts
|
|
uint256 actualTakerAmount =
|
|
filledTakerAmount == 0 ? order.taker_amount : filledTakerAmount;
|
|
uint256 actualMakerAmount = filledTakerAmount == 0
|
|
? order.maker_amount
|
|
: (order.maker_amount * filledTakerAmount) / order.taker_amount;
|
|
|
|
// Transfer taker tokens from msg.sender to maker
|
|
if (order.taker_token == address(0)) {
|
|
// ETH transfer
|
|
require(msg.value == actualTakerAmount, "Incorrect ETH amount");
|
|
payable(order.maker_address).transfer(actualTakerAmount);
|
|
} else {
|
|
// ERC20 transfer
|
|
IERC20(order.taker_token).transferFrom(
|
|
msg.sender, order.maker_address, actualTakerAmount
|
|
);
|
|
}
|
|
|
|
// Transfer maker tokens from maker to receiver
|
|
if (order.maker_token == address(0)) {
|
|
// ETH transfer - this shouldn't happen in practice
|
|
revert("ETH output not supported");
|
|
} else {
|
|
// In the real contract, maker would need to have tokens and approve
|
|
// For testing, we'll check if maker has balance, if not we assume they're funded
|
|
uint256 makerBalance =
|
|
IERC20(order.maker_token).balanceOf(order.maker_address);
|
|
if (makerBalance < actualMakerAmount) {
|
|
revert InsufficientMakerBalance();
|
|
}
|
|
|
|
// Transfer from maker to receiver
|
|
// This assumes the maker has pre-approved the settlement contract
|
|
IERC20(order.maker_token).transferFrom(
|
|
order.maker_address, order.receiver, actualMakerAmount
|
|
);
|
|
}
|
|
}
|
|
|
|
function swapAggregate(
|
|
IBebopSettlement.Aggregate calldata order,
|
|
IBebopSettlement.MakerSignature[] calldata makerSignatures,
|
|
uint256 filledTakerAmount
|
|
) external payable {
|
|
// Check order expiry
|
|
if (block.timestamp > order.expiry) revert OrderExpired();
|
|
|
|
// Check we have at least one maker
|
|
if (makerSignatures.length == 0) revert InvalidSignature();
|
|
|
|
// For testing, we'll do a simplified signature validation
|
|
for (uint256 i = 0; i < makerSignatures.length; i++) {
|
|
if (makerSignatures[i].signatureBytes.length < 4) {
|
|
revert InvalidSignature();
|
|
}
|
|
}
|
|
|
|
// Aggregate orders only support full fills
|
|
require(
|
|
filledTakerAmount == 0,
|
|
"Partial fills not supported for aggregate orders"
|
|
);
|
|
|
|
// Transfer taker tokens from msg.sender to makers
|
|
for (uint256 i = 0; i < order.taker_tokens.length; i++) {
|
|
uint256 takerAmount = order.taker_amounts[i];
|
|
// Split proportionally among makers
|
|
for (uint256 j = 0; j < order.maker_addresses.length; j++) {
|
|
uint256 makerShare = takerAmount / order.maker_addresses.length;
|
|
if (j == order.maker_addresses.length - 1) {
|
|
// Last maker gets any remainder
|
|
makerShare = takerAmount
|
|
- (makerShare * (order.maker_addresses.length - 1));
|
|
}
|
|
IERC20(order.taker_tokens[i]).transferFrom(
|
|
msg.sender, order.maker_addresses[j], makerShare
|
|
);
|
|
}
|
|
}
|
|
|
|
// Transfer maker tokens from each maker to receiver
|
|
for (uint256 i = 0; i < order.maker_addresses.length; i++) {
|
|
address maker = order.maker_addresses[i];
|
|
|
|
// Fund maker with tokens if they don't have enough (for testing)
|
|
for (uint256 j = 0; j < order.maker_tokens[i].length; j++) {
|
|
address token = order.maker_tokens[i][j];
|
|
uint256 amount = order.maker_amounts[i][j];
|
|
|
|
uint256 makerBalance = IERC20(token).balanceOf(maker);
|
|
if (makerBalance < amount) {
|
|
revert InsufficientMakerBalance();
|
|
}
|
|
|
|
// Transfer from maker to receiver
|
|
IERC20(token).transferFrom(maker, order.receiver, amount);
|
|
}
|
|
}
|
|
}
|
|
}
|