test: add BebopSettlementMock and update test setup
This commit is contained in:
@@ -55,6 +55,7 @@ contract Constants is Test, BaseConstants {
|
|||||||
address WTAO_ADDR = address(0x77E06c9eCCf2E797fd462A92B6D7642EF85b0A44);
|
address WTAO_ADDR = address(0x77E06c9eCCf2E797fd462A92B6D7642EF85b0A44);
|
||||||
address BSGG_ADDR = address(0xdA16Cf041E2780618c49Dbae5d734B89a6Bac9b3);
|
address BSGG_ADDR = address(0xdA16Cf041E2780618c49Dbae5d734B89a6Bac9b3);
|
||||||
address GHO_ADDR = address(0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f);
|
address GHO_ADDR = address(0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f);
|
||||||
|
address ONDO_ADDR = address(0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3);
|
||||||
|
|
||||||
// Maverick v2
|
// Maverick v2
|
||||||
address MAVERICK_V2_FACTORY = 0x0A7e848Aca42d879EF06507Fca0E7b33A0a63c1e;
|
address MAVERICK_V2_FACTORY = 0x0A7e848Aca42d879EF06507Fca0E7b33A0a63c1e;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {Permit2TestHelper} from "./Permit2TestHelper.sol";
|
|||||||
import "./TestUtils.sol";
|
import "./TestUtils.sol";
|
||||||
import {MaverickV2Executor} from "../src/executors/MaverickV2Executor.sol";
|
import {MaverickV2Executor} from "../src/executors/MaverickV2Executor.sol";
|
||||||
import {BalancerV3Executor} from "../src/executors/BalancerV3Executor.sol";
|
import {BalancerV3Executor} from "../src/executors/BalancerV3Executor.sol";
|
||||||
import {MockBebopSettlement} from "./executors/BebopExecutor.t.sol";
|
import {BebopSettlementMock} from "./mock/BebopSettlementMock.sol";
|
||||||
|
|
||||||
contract TychoRouterExposed is TychoRouter {
|
contract TychoRouterExposed is TychoRouter {
|
||||||
constructor(address _permit2, address weth) TychoRouter(_permit2, weth) {}
|
constructor(address _permit2, address weth) TychoRouter(_permit2, weth) {}
|
||||||
@@ -91,6 +91,13 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils {
|
|||||||
tychoRouter.setExecutors(executors);
|
tychoRouter.setExecutors(executors);
|
||||||
vm.stopPrank();
|
vm.stopPrank();
|
||||||
|
|
||||||
|
// Deploy our mock Bebop settlement and use vm.etch to replace the real one
|
||||||
|
// This avoids InvalidSender errors since the mock doesn't validate taker addresses
|
||||||
|
// Do this AFTER deploying executors to preserve deterministic addresses
|
||||||
|
BebopSettlementMock mockSettlement = new BebopSettlementMock();
|
||||||
|
bytes memory mockCode = address(mockSettlement).code;
|
||||||
|
vm.etch(BEBOP_SETTLEMENT, mockCode);
|
||||||
|
|
||||||
vm.startPrank(BOB);
|
vm.startPrank(BOB);
|
||||||
tokens.push(new MockERC20("Token A", "A"));
|
tokens.push(new MockERC20("Token A", "A"));
|
||||||
tokens.push(new MockERC20("Token B", "B"));
|
tokens.push(new MockERC20("Token B", "B"));
|
||||||
@@ -135,11 +142,7 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils {
|
|||||||
maverickv2Executor =
|
maverickv2Executor =
|
||||||
new MaverickV2Executor(MAVERICK_V2_FACTORY, PERMIT2_ADDRESS);
|
new MaverickV2Executor(MAVERICK_V2_FACTORY, PERMIT2_ADDRESS);
|
||||||
balancerV3Executor = new BalancerV3Executor(PERMIT2_ADDRESS);
|
balancerV3Executor = new BalancerV3Executor(PERMIT2_ADDRESS);
|
||||||
|
bebopExecutor = new BebopExecutor(BEBOP_SETTLEMENT, PERMIT2_ADDRESS);
|
||||||
// Deploy mock Bebop settlement for testing
|
|
||||||
MockBebopSettlement mockBebopSettlement = new MockBebopSettlement();
|
|
||||||
bebopExecutor =
|
|
||||||
new BebopExecutor(address(mockBebopSettlement), PERMIT2_ADDRESS);
|
|
||||||
|
|
||||||
address[] memory executors = new address[](10);
|
address[] memory executors = new address[](10);
|
||||||
executors[0] = address(usv2Executor);
|
executors[0] = address(usv2Executor);
|
||||||
|
|||||||
147
foundry/test/mock/BebopSettlementMock.sol
Normal file
147
foundry/test/mock/BebopSettlementMock.sol
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user