176 lines
5.9 KiB
Solidity
176 lines
5.9 KiB
Solidity
// SPDX-License-Identifier: BUSL-1.1
|
|
pragma solidity ^0.8.10;
|
|
|
|
import "../../src/executors/BebopExecutor.sol";
|
|
import {Test, console} from "forge-std/Test.sol";
|
|
import "@openzeppelin/contracts/utils/Address.sol";
|
|
|
|
contract BebopExecutorHarness is BebopExecutor, Test {
|
|
using SafeERC20 for IERC20;
|
|
using Address for address;
|
|
|
|
constructor(address _bebopSettlement, address _permit2)
|
|
BebopExecutor(_bebopSettlement, _permit2)
|
|
{}
|
|
|
|
/// @dev Helper function to strip selector from bytes using assembly
|
|
function _stripSelector(bytes memory data)
|
|
internal
|
|
pure
|
|
returns (bytes memory bebopCalldataWithoutSelector)
|
|
{
|
|
require(data.length >= 4, "BE: data too short for selector");
|
|
|
|
// Create new array with length - 4
|
|
bebopCalldataWithoutSelector = new bytes(data.length - 4);
|
|
|
|
assembly {
|
|
// Get pointers to the data
|
|
let srcPtr := add(data, 0x24) // Skip length (0x20) and selector (0x04)
|
|
let destPtr := add(bebopCalldataWithoutSelector, 0x20) // Skip length
|
|
|
|
// Copy all bytes after the selector
|
|
let length := sub(mload(data), 4)
|
|
|
|
// Copy word by word for efficiency
|
|
let words := div(length, 32)
|
|
let remainder := mod(length, 32)
|
|
|
|
// Copy full words
|
|
for { let i := 0 } lt(i, words) { i := add(i, 1) } {
|
|
mstore(add(destPtr, mul(i, 32)), mload(add(srcPtr, mul(i, 32))))
|
|
}
|
|
|
|
// Copy remaining bytes if any
|
|
if remainder {
|
|
let lastWord := mload(add(srcPtr, mul(words, 32)))
|
|
mstore(add(destPtr, mul(words, 32)), lastWord)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Expose the internal decodeData function for testing
|
|
function decodeParams(bytes calldata data)
|
|
external
|
|
pure
|
|
returns (
|
|
address tokenIn,
|
|
address tokenOut,
|
|
TransferType transferType,
|
|
bytes memory bebopCalldata,
|
|
uint8 partialFillOffset,
|
|
uint256 originalFilledTakerAmount,
|
|
bool approvalNeeded,
|
|
address receiver
|
|
)
|
|
{
|
|
return _decodeData(data);
|
|
}
|
|
|
|
// Expose the internal modifyFilledTakerAmount function for testing
|
|
function exposed_modifyFilledTakerAmount(
|
|
bytes memory bebopCalldata,
|
|
uint256 givenAmount,
|
|
uint256 originalFilledTakerAmount,
|
|
uint8 partialFillOffset
|
|
) external pure returns (bytes memory) {
|
|
return _modifyFilledTakerAmount(
|
|
bebopCalldata,
|
|
givenAmount,
|
|
originalFilledTakerAmount,
|
|
partialFillOffset
|
|
);
|
|
}
|
|
|
|
// Override swap to handle test setup
|
|
function swap(uint256 givenAmount, bytes calldata data)
|
|
external
|
|
payable
|
|
override
|
|
returns (uint256 calculatedAmount)
|
|
{
|
|
// Decode the packed data
|
|
(
|
|
address tokenIn,
|
|
,
|
|
TransferType transferType,
|
|
bytes memory bebopCalldata,
|
|
, // partialFillOffset not needed in test harness
|
|
uint256 originalFilledTakerAmount,
|
|
, // approvalNeeded not needed in test harness
|
|
// receiver not needed since we extract it from bebop calldata
|
|
) = _decodeData(data);
|
|
|
|
// Extract taker address, receiver, and expiry from bebop calldata
|
|
bytes4 sel = _getSelector(bebopCalldata);
|
|
address takerAddress;
|
|
address receiverAddress;
|
|
uint256 expiry;
|
|
|
|
bytes memory bebopCalldataWithoutSelector =
|
|
_stripSelector(bebopCalldata);
|
|
|
|
if (sel == SWAP_SINGLE_SELECTOR) {
|
|
(IBebopSettlement.Single memory order,,) = abi.decode(
|
|
bebopCalldataWithoutSelector,
|
|
(
|
|
IBebopSettlement.Single,
|
|
IBebopSettlement.MakerSignature,
|
|
uint256
|
|
)
|
|
);
|
|
takerAddress = order.taker_address;
|
|
receiverAddress = order.receiver;
|
|
expiry = order.expiry;
|
|
} else {
|
|
(IBebopSettlement.Aggregate memory order,,) = abi.decode(
|
|
bebopCalldataWithoutSelector,
|
|
(
|
|
IBebopSettlement.Aggregate,
|
|
IBebopSettlement.MakerSignature[],
|
|
uint256
|
|
)
|
|
);
|
|
takerAddress = order.taker_address;
|
|
receiverAddress = order.receiver;
|
|
expiry = order.expiry;
|
|
}
|
|
|
|
uint256 actualFilledTakerAmount = originalFilledTakerAmount
|
|
> givenAmount ? givenAmount : originalFilledTakerAmount;
|
|
|
|
// For testing: transfer tokens from executor to taker address
|
|
// This simulates the taker having the tokens with approval
|
|
if (tokenIn != address(0)) {
|
|
// The executor already has the tokens from the test, just transfer to taker
|
|
IERC20(tokenIn).safeTransfer(takerAddress, actualFilledTakerAmount);
|
|
|
|
// Approve settlement from taker's perspective
|
|
// Stop any existing prank first
|
|
vm.stopPrank();
|
|
vm.startPrank(takerAddress);
|
|
IERC20(tokenIn).forceApprove(bebopSettlement, type(uint256).max);
|
|
vm.stopPrank();
|
|
} else {
|
|
vm.stopPrank();
|
|
// For native ETH, deal it to the taker address
|
|
payable(takerAddress).transfer(actualFilledTakerAmount);
|
|
}
|
|
|
|
// IMPORTANT: Prank as the taker address to pass the settlement validation
|
|
vm.stopPrank();
|
|
vm.startPrank(takerAddress);
|
|
|
|
// Set block timestamp to ensure order is valid regardless of fork block
|
|
uint256 currentTimestamp = block.timestamp;
|
|
vm.warp(expiry - 1); // Set timestamp to just before expiry
|
|
|
|
// Call the parent's internal _swap function
|
|
calculatedAmount = _swap(givenAmount, data);
|
|
|
|
// Restore original timestamp
|
|
vm.warp(currentTimestamp);
|
|
vm.stopPrank();
|
|
}
|
|
}
|