Files
tycho-execution/foundry/test/protocols/LiquidityParty.t.sol
2025-12-10 14:47:04 -04:00

166 lines
5.6 KiB
Solidity

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.26;
import {
IERC20
} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {
SafeERC20
} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import {RestrictTransferFrom} from "../../src/RestrictTransferFrom.sol";
import {
LiquidityPartyExecutor,
IPartyPool
} from "../../src/executors/LiquidityPartyExecutor.sol";
import {Constants} from "../Constants.sol";
import {Permit2TestHelper} from "../Permit2TestHelper.sol";
import {TestUtils} from "../TestUtils.sol";
import {LiquidityPartyExecutorExposed} from "./LiquidityParty.t.sol";
contract LiquidityPartyExecutorExposed is LiquidityPartyExecutor {
constructor(address _permit2) LiquidityPartyExecutor(_permit2) {}
function decodeParams(bytes calldata data)
external
pure
returns (
IPartyPool pool,
address tokenIn,
uint8 indexIn,
uint8 indexOut,
TransferType transferType,
address receiver
)
{
return _decodeData(data);
}
}
contract LiquidityPartyExecutorTest is Constants, Permit2TestHelper, TestUtils {
using SafeERC20 for IERC20;
LiquidityPartyExecutorExposed private executor;
IPartyPool private constant POOL =
IPartyPool(0x2A804e94500AE379ee0CcC423a67B07cc0aF548C);
IERC20 private constant INPUT_TOKEN =
IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); // WETH
uint8 private constant INPUT_INDEX = 3;
uint256 private constant AMOUNT_IN = 30428379889; // 30 gwei, 0.1% of the pool's WETH
IERC20 private constant OUTPUT_TOKEN =
IERC20(0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE); // SHIB
uint8 private constant OUTPUT_INDEX = 9;
uint256 private constant EXPECTED_AMOUNT_OUT = 11480220066406156603; // about 115 SHIB
uint256 private constant FORK_BLOCK = 23978797;
function setUp() public {
vm.createSelectFork(vm.rpcUrl("mainnet"), FORK_BLOCK);
executor = new LiquidityPartyExecutorExposed(PERMIT2_ADDRESS);
}
function testDecodeParams() public view {
bytes memory params = abi.encodePacked(
POOL,
INPUT_TOKEN,
INPUT_INDEX,
OUTPUT_INDEX,
ALICE,
RestrictTransferFrom.TransferType.Transfer
);
(
IPartyPool pool,
address tokenIn,
uint8 indexIn,
uint8 indexOut,
RestrictTransferFrom.TransferType transferType,
address receiver
) = executor.decodeParams(params);
assertEq(address(pool), address(POOL));
assertEq(address(tokenIn), address(INPUT_TOKEN));
assertEq(indexIn, INPUT_INDEX);
assertEq(indexOut, OUTPUT_INDEX);
assertEq(
uint8(transferType),
uint8(RestrictTransferFrom.TransferType.Transfer)
);
assertEq(receiver, ALICE);
}
function testDecodeParamsInvalidDataLength() public {
bytes memory invalidParams =
abi.encodePacked(WETH_ADDR, address(2), address(3));
vm.expectRevert();
executor.decodeParams(invalidParams);
}
function testSwapWithTransfer() public {
bytes memory protocolData = abi.encodePacked(
POOL,
INPUT_TOKEN,
INPUT_INDEX,
OUTPUT_INDEX,
BOB,
RestrictTransferFrom.TransferType.Transfer
);
deal(address(INPUT_TOKEN), address(executor), AMOUNT_IN);
uint256 amountOut = executor.swap(AMOUNT_IN, protocolData);
assertEq(amountOut, EXPECTED_AMOUNT_OUT);
assertGe(OUTPUT_TOKEN.balanceOf(BOB), EXPECTED_AMOUNT_OUT);
}
function testSwapNoTransfer() public {
bytes memory protocolData = abi.encodePacked(
POOL,
INPUT_TOKEN,
INPUT_INDEX,
OUTPUT_INDEX,
BOB,
RestrictTransferFrom.TransferType.None
);
deal(address(INPUT_TOKEN), address(this), AMOUNT_IN);
/// forge-lint: disable-next-line(erc20-unchecked-transfer)
INPUT_TOKEN.transfer(address(POOL), AMOUNT_IN);
uint256 amountOut = executor.swap(AMOUNT_IN, protocolData);
assertEq(amountOut, EXPECTED_AMOUNT_OUT);
assertGe(OUTPUT_TOKEN.balanceOf(BOB), EXPECTED_AMOUNT_OUT);
}
function testSwapIntegration() public {
bytes memory protocolData =
loadCallDataFromFile("test_encode_liquidityparty");
deal(address(INPUT_TOKEN), address(executor), AMOUNT_IN);
uint256 amountOut = executor.swap(AMOUNT_IN, protocolData);
uint256 finalBalance = OUTPUT_TOKEN.balanceOf(BOB);
assertEq(amountOut, EXPECTED_AMOUNT_OUT);
assertGe(finalBalance, amountOut);
}
function testSwapFailureKilledPool() public {
// Killed pools should not even appear as protocol components, but we test an attempted swap anyway.
// This address is a pool that was killed (permanent redeem-only mode)
address killedPool = address(0xC0A908477FFeff658699182bEB5EcaF1D46B3ddB);
bytes memory protocolData = abi.encodePacked(
killedPool,
INPUT_TOKEN,
INPUT_INDEX,
OUTPUT_INDEX,
BOB,
RestrictTransferFrom.TransferType.None
);
deal(address(INPUT_TOKEN), address(executor), AMOUNT_IN);
vm.expectRevert();
executor.swap(AMOUNT_IN, protocolData);
}
function testExportContract() public {
exportRuntimeBytecode(address(executor), "LiquidityParty");
}
}