native currency fixes
This commit is contained in:
@@ -18,6 +18,10 @@ library Deploy {
|
||||
|
||||
function newPartyPlanner() internal returns (PartyPlanner) {
|
||||
IWETH9 wrapper = new WETH9();
|
||||
return newPartyPlanner(wrapper);
|
||||
}
|
||||
|
||||
function newPartyPlanner(IWETH9 wrapper) internal returns (PartyPlanner) {
|
||||
return new PartyPlanner(
|
||||
wrapper,
|
||||
new PartyPoolSwapImpl(wrapper),
|
||||
@@ -40,6 +44,20 @@ library Deploy {
|
||||
bool _stable
|
||||
) internal returns (PartyPool) {
|
||||
IWETH9 wrapper = new WETH9();
|
||||
return newPartyPool(name_, symbol_, tokens_, bases_, _kappa, _swapFeePpm, _flashFeePpm, wrapper, _stable);
|
||||
}
|
||||
|
||||
function newPartyPool(
|
||||
string memory name_,
|
||||
string memory symbol_,
|
||||
IERC20[] memory tokens_,
|
||||
uint256[] memory bases_,
|
||||
int128 _kappa,
|
||||
uint256 _swapFeePpm,
|
||||
uint256 _flashFeePpm,
|
||||
IWETH9 wrapper,
|
||||
bool _stable
|
||||
) internal returns (PartyPool) {
|
||||
return _stable && tokens_.length == 2 ?
|
||||
new PartyPoolBalancedPair(
|
||||
name_,
|
||||
|
||||
612
test/NativeTest.t.sol
Normal file
612
test/NativeTest.t.sol
Normal file
@@ -0,0 +1,612 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
/* solhint-disable */
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import "forge-std/Test.sol";
|
||||
import "@abdk/ABDKMath64x64.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "../src/LMSRStabilized.sol";
|
||||
import "../src/PartyPool.sol";
|
||||
import {IWETH9} from "../src/IWETH9.sol";
|
||||
import {PartyPlanner} from "../src/PartyPlanner.sol";
|
||||
import {Deploy} from "./Deploy.sol";
|
||||
import {PartyPoolViewer} from "../src/PartyPoolViewer.sol";
|
||||
import {WETH9} from "./WETH9.sol";
|
||||
|
||||
/// @notice Minimal ERC20 token for tests with an external mint function.
|
||||
contract TestERC20Native is ERC20 {
|
||||
constructor(string memory name_, string memory symbol_, uint256 initialSupply) ERC20(name_, symbol_) {
|
||||
if (initialSupply > 0) {
|
||||
_mint(msg.sender, initialSupply);
|
||||
}
|
||||
}
|
||||
|
||||
function mint(address to, uint256 amount) external {
|
||||
_mint(to, amount);
|
||||
}
|
||||
|
||||
// Expose convenient approve helper for tests
|
||||
function approveMax(address spender) external {
|
||||
_approve(msg.sender, spender, type(uint256).max);
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice Tests for PartyPool native currency (ETH) functionality with WETH wrapping/unwrapping.
|
||||
/// @dev This test contract creates a pool where one of the assets is WETH, then tests all operations
|
||||
/// that can send or receive native currency by using unwrap=true and {value:amount} syntax.
|
||||
contract NativeTest is Test {
|
||||
using ABDKMath64x64 for int128;
|
||||
|
||||
TestERC20Native token0;
|
||||
TestERC20Native token1;
|
||||
WETH9 weth; // WETH is our third token
|
||||
PartyPool pool;
|
||||
PartyPoolViewer viewer;
|
||||
|
||||
address alice;
|
||||
address bob;
|
||||
|
||||
// Common parameters
|
||||
int128 tradeFrac;
|
||||
int128 targetSlippage;
|
||||
|
||||
uint256 constant INIT_BAL = 1_000_000; // initial token units for each token
|
||||
uint256 constant BASE = 1; // use base=1 so internal amounts correspond to raw integers
|
||||
|
||||
function setUp() public {
|
||||
alice = address(0xA11ce);
|
||||
bob = address(0xB0b);
|
||||
|
||||
// Give alice and bob native currency for testing
|
||||
vm.deal(alice, 100 ether);
|
||||
vm.deal(bob, 100 ether);
|
||||
|
||||
// Deploy two regular ERC20 tokens
|
||||
token0 = new TestERC20Native("T0", "T0", 0);
|
||||
token1 = new TestERC20Native("T1", "T1", 0);
|
||||
|
||||
// Deploy WETH
|
||||
weth = new WETH9();
|
||||
|
||||
// Mint initial balances to this test contract
|
||||
token0.mint(address(this), INIT_BAL);
|
||||
token1.mint(address(this), INIT_BAL);
|
||||
|
||||
// For WETH, we deposit native currency to get wrapped tokens
|
||||
weth.deposit{value: INIT_BAL}();
|
||||
|
||||
// Configure LMSR parameters
|
||||
tradeFrac = ABDKMath64x64.divu(100, 10_000); // 0.01
|
||||
targetSlippage = ABDKMath64x64.divu(10, 10_000); // 0.001
|
||||
|
||||
// Build arrays for pool constructor: [token0, token1, WETH]
|
||||
IERC20[] memory tokens = new IERC20[](3);
|
||||
tokens[0] = IERC20(address(token0));
|
||||
tokens[1] = IERC20(address(token1));
|
||||
tokens[2] = IERC20(address(weth)); // WETH as third token
|
||||
|
||||
uint256[] memory bases = new uint256[](3);
|
||||
bases[0] = BASE;
|
||||
bases[1] = BASE;
|
||||
bases[2] = BASE;
|
||||
|
||||
// Deploy pool with a small fee (0.1%)
|
||||
uint256 feePpm = 1000;
|
||||
|
||||
int128 kappa = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
||||
pool = Deploy.newPartyPool("LP", "LP", tokens, bases, kappa, feePpm, feePpm, weth, false);
|
||||
|
||||
// Transfer initial deposit amounts into pool
|
||||
token0.transfer(address(pool), INIT_BAL);
|
||||
token1.transfer(address(pool), INIT_BAL);
|
||||
weth.transfer(address(pool), INIT_BAL);
|
||||
|
||||
// Perform initial mint
|
||||
pool.initialMint(address(this), 0);
|
||||
|
||||
// Mint tokens to alice and bob for testing
|
||||
token0.mint(alice, INIT_BAL);
|
||||
token1.mint(alice, INIT_BAL);
|
||||
|
||||
token0.mint(bob, INIT_BAL);
|
||||
token1.mint(bob, INIT_BAL);
|
||||
|
||||
viewer = Deploy.newViewer();
|
||||
}
|
||||
|
||||
/// @notice Helper to verify refunds work correctly
|
||||
modifier expectRefund(address user, uint256 sent, uint256 expectedUsed) {
|
||||
uint256 balBefore = user.balance;
|
||||
_;
|
||||
uint256 balAfter = user.balance;
|
||||
uint256 refund = sent - expectedUsed;
|
||||
assertEq(balAfter, balBefore - expectedUsed, "User should be refunded unused native currency");
|
||||
}
|
||||
|
||||
/* ----------------------
|
||||
Swap Tests with Native Currency
|
||||
---------------------- */
|
||||
|
||||
/// @notice Test swap with native currency as input (token index 2 = WETH)
|
||||
/// @dev Send ETH to pool, which should wrap it as WETH and execute the swap
|
||||
function testSwapWithNativeInput() public {
|
||||
uint256 maxIn = 10_000;
|
||||
|
||||
// Alice swaps native currency (ETH -> WETH input) for token0 output
|
||||
vm.startPrank(alice);
|
||||
|
||||
uint256 aliceEthBefore = alice.balance;
|
||||
uint256 aliceToken0Before = token0.balanceOf(alice);
|
||||
|
||||
// Execute swap: WETH (index 2) -> token0 (index 0)
|
||||
// Send native currency with {value: maxIn}
|
||||
(uint256 amountIn, uint256 amountOut, uint256 fee) = pool.swap{value: maxIn}(
|
||||
alice, // payer
|
||||
alice, // receiver
|
||||
2, // inputTokenIndex (WETH)
|
||||
0, // outputTokenIndex (token0)
|
||||
maxIn, // maxAmountIn
|
||||
0, // limitPrice
|
||||
0, // deadline
|
||||
false // unwrap (output is not WETH, so false)
|
||||
);
|
||||
|
||||
// Verify amounts
|
||||
assertTrue(amountIn > 0, "expected some input used");
|
||||
assertTrue(amountOut > 0, "expected some output returned");
|
||||
assertTrue(amountIn <= maxIn, "used input must not exceed max");
|
||||
|
||||
// Alice's ETH balance should decrease by amountIn
|
||||
assertEq(alice.balance, aliceEthBefore - amountIn, "Alice ETH should decrease by amountIn");
|
||||
|
||||
// Alice's token0 balance should increase by amountOut
|
||||
assertEq(token0.balanceOf(alice), aliceToken0Before + amountOut, "Alice token0 should increase");
|
||||
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
/// @notice Test swap with native currency as output (unwrap=true)
|
||||
/// @dev Swap token0 for WETH, then unwrap WETH to native currency
|
||||
function testSwapWithNativeOutput() public {
|
||||
uint256 maxIn = 10_000;
|
||||
|
||||
vm.startPrank(alice);
|
||||
token0.approve(address(pool), type(uint256).max);
|
||||
|
||||
uint256 aliceToken0Before = token0.balanceOf(alice);
|
||||
uint256 aliceEthBefore = alice.balance;
|
||||
|
||||
// Execute swap: token0 (index 0) -> WETH (index 2) with unwrap=true
|
||||
(uint256 amountIn, uint256 amountOut, uint256 fee) = pool.swap(
|
||||
alice, // payer
|
||||
alice, // receiver
|
||||
0, // inputTokenIndex (token0)
|
||||
2, // outputTokenIndex (WETH)
|
||||
maxIn, // maxAmountIn
|
||||
0, // limitPrice
|
||||
0, // deadline
|
||||
true // unwrap (receive native currency instead of WETH)
|
||||
);
|
||||
|
||||
// Verify amounts
|
||||
assertTrue(amountIn > 0, "expected some input used");
|
||||
assertTrue(amountOut > 0, "expected some output returned");
|
||||
|
||||
// Alice's token0 balance should decrease by amountIn
|
||||
assertEq(token0.balanceOf(alice), aliceToken0Before - amountIn, "Alice token0 should decrease");
|
||||
|
||||
// Alice's ETH balance should increase by amountOut (unwrapped)
|
||||
assertEq(alice.balance, aliceEthBefore + amountOut, "Alice ETH should increase by unwrapped amount");
|
||||
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
/// @notice Test swap with excess native currency sent - verify refund
|
||||
function testSwapWithExcessNativeRefunded() public {
|
||||
uint256 maxIn = 10_000;
|
||||
uint256 excessAmount = 5_000;
|
||||
uint256 totalSent = maxIn + excessAmount;
|
||||
|
||||
vm.startPrank(alice);
|
||||
|
||||
uint256 aliceEthBefore = alice.balance;
|
||||
|
||||
// Execute swap with excess native currency
|
||||
(uint256 amountIn, uint256 amountOut, uint256 fee) = pool.swap{value: totalSent}(
|
||||
alice, // payer
|
||||
alice, // receiver
|
||||
2, // inputTokenIndex (WETH)
|
||||
0, // outputTokenIndex (token0)
|
||||
maxIn, // maxAmountIn
|
||||
0, // limitPrice
|
||||
0, // deadline
|
||||
false // unwrap
|
||||
);
|
||||
|
||||
// Verify that only amountIn was used, and excess was refunded
|
||||
assertTrue(amountIn <= maxIn, "used input must not exceed max");
|
||||
uint256 expectedRefund = totalSent - amountIn;
|
||||
assertEq(alice.balance, aliceEthBefore - amountIn, "Alice should be refunded excess ETH");
|
||||
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
/// @notice Test swapToLimit with native currency input
|
||||
function testSwapToLimitWithNativeInput() public {
|
||||
// Choose a limit price slightly above current (~1)
|
||||
int128 limitPrice = ABDKMath64x64.fromInt(1).add(ABDKMath64x64.divu(1, 1000));
|
||||
|
||||
vm.startPrank(alice);
|
||||
|
||||
uint256 aliceEthBefore = alice.balance;
|
||||
|
||||
// Execute swapToLimit: WETH (index 2) -> token0 (index 0)
|
||||
// Send a large amount of native currency; pool will only use what's needed
|
||||
uint256 largeAmount = 100_000;
|
||||
(uint256 amountInUsed, uint256 amountOut, uint256 fee) = pool.swapToLimit{value: largeAmount}(
|
||||
alice, // payer
|
||||
alice, // receiver
|
||||
2, // inputTokenIndex (WETH)
|
||||
0, // outputTokenIndex (token0)
|
||||
limitPrice, // limitPrice
|
||||
0, // deadline
|
||||
false // unwrap
|
||||
);
|
||||
|
||||
assertTrue(amountInUsed > 0, "expected some input used for swapToLimit");
|
||||
assertTrue(amountOut > 0, "expected some output for swapToLimit");
|
||||
|
||||
// Alice should be refunded unused ETH
|
||||
assertTrue(alice.balance > aliceEthBefore - largeAmount, "Alice should be refunded");
|
||||
assertEq(alice.balance, aliceEthBefore - amountInUsed - fee, "Alice ETH balance check");
|
||||
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
/// @notice Test swapToLimit with native currency output (unwrap=true)
|
||||
function testSwapToLimitWithNativeOutput() public {
|
||||
int128 limitPrice = ABDKMath64x64.fromInt(1).add(ABDKMath64x64.divu(1, 1000));
|
||||
|
||||
vm.startPrank(alice);
|
||||
token0.approve(address(pool), type(uint256).max);
|
||||
|
||||
uint256 aliceEthBefore = alice.balance;
|
||||
|
||||
// Execute swapToLimit: token0 (index 0) -> WETH (index 2) with unwrap=true
|
||||
(uint256 amountInUsed, uint256 amountOut, uint256 fee) = pool.swapToLimit(
|
||||
alice, // payer
|
||||
alice, // receiver
|
||||
0, // inputTokenIndex (token0)
|
||||
2, // outputTokenIndex (WETH)
|
||||
limitPrice, // limitPrice
|
||||
0, // deadline
|
||||
true // unwrap (receive native currency)
|
||||
);
|
||||
|
||||
assertTrue(amountInUsed > 0, "expected some input used");
|
||||
assertTrue(amountOut > 0, "expected some output");
|
||||
|
||||
// Alice should receive native currency
|
||||
assertTrue(alice.balance > aliceEthBefore, "Alice should receive ETH");
|
||||
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
/* ----------------------
|
||||
Mint Tests with Native Currency
|
||||
---------------------- */
|
||||
|
||||
/// @notice Test proportional mint with native currency input
|
||||
function testMintWithNativeInput() public {
|
||||
uint256 lpRequest = pool.totalSupply() / 10; // Request 10% of pool
|
||||
|
||||
// Get required deposit amounts
|
||||
uint256[] memory deposits = viewer.mintAmounts(pool, lpRequest);
|
||||
|
||||
vm.startPrank(alice);
|
||||
token0.approve(address(pool), type(uint256).max);
|
||||
token1.approve(address(pool), type(uint256).max);
|
||||
// For WETH, we send native currency instead of approving
|
||||
|
||||
uint256 aliceEthBefore = alice.balance;
|
||||
uint256 wethDeposit = deposits[2]; // WETH is index 2
|
||||
|
||||
// Perform mint with native currency for WETH portion
|
||||
uint256 lpMinted = pool.mint{value: wethDeposit}(
|
||||
alice, // payer
|
||||
alice, // receiver
|
||||
lpRequest, // lpTokenAmount
|
||||
0 // deadline
|
||||
);
|
||||
|
||||
assertTrue(lpMinted > 0, "LP should be minted");
|
||||
|
||||
// Alice's ETH should decrease by WETH deposit amount
|
||||
assertEq(alice.balance, aliceEthBefore - wethDeposit, "Alice ETH should decrease");
|
||||
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
/// @notice Test mint with excess native currency - verify refund
|
||||
function testMintWithExcessNativeRefunded() public {
|
||||
uint256 lpRequest = pool.totalSupply() / 10;
|
||||
uint256[] memory deposits = viewer.mintAmounts(pool, lpRequest);
|
||||
|
||||
vm.startPrank(alice);
|
||||
token0.approve(address(pool), type(uint256).max);
|
||||
token1.approve(address(pool), type(uint256).max);
|
||||
|
||||
uint256 aliceEthBefore = alice.balance;
|
||||
uint256 wethDeposit = deposits[2];
|
||||
uint256 excess = 10_000;
|
||||
uint256 totalSent = wethDeposit + excess;
|
||||
|
||||
// Send excess native currency
|
||||
uint256 lpMinted = pool.mint{value: totalSent}(
|
||||
alice,
|
||||
alice,
|
||||
lpRequest,
|
||||
0
|
||||
);
|
||||
|
||||
assertTrue(lpMinted > 0, "LP should be minted");
|
||||
|
||||
// Alice should be refunded the excess
|
||||
assertEq(alice.balance, aliceEthBefore - wethDeposit, "Alice should be refunded excess");
|
||||
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
/* ----------------------
|
||||
Burn Tests with Native Currency
|
||||
---------------------- */
|
||||
|
||||
/// @notice Test burn with native currency output (unwrap=true)
|
||||
function testBurnWithNativeOutput() public {
|
||||
uint256 lpToBurn = pool.totalSupply() / 10;
|
||||
|
||||
// Get expected withdraw amounts
|
||||
uint256[] memory withdraws = viewer.burnAmounts(pool, lpToBurn);
|
||||
|
||||
uint256 thisEthBefore = address(this).balance;
|
||||
uint256 expectedWethWithdraw = withdraws[2]; // WETH is index 2
|
||||
|
||||
// Burn LP with unwrap=true to receive native currency for WETH portion
|
||||
uint256[] memory actualWithdraws = pool.burn(
|
||||
address(this), // payer (this contract holds LP from setUp)
|
||||
address(this), // receiver
|
||||
lpToBurn, // lpAmount
|
||||
0, // deadline
|
||||
true // unwrap (receive native currency for WETH)
|
||||
);
|
||||
|
||||
// Verify we received the expected amounts
|
||||
assertEq(actualWithdraws[0], withdraws[0], "token0 withdraw amount");
|
||||
assertEq(actualWithdraws[1], withdraws[1], "token1 withdraw amount");
|
||||
assertEq(actualWithdraws[2], withdraws[2], "WETH withdraw amount");
|
||||
|
||||
// Verify we received native currency for WETH portion
|
||||
assertEq(address(this).balance, thisEthBefore + expectedWethWithdraw, "Should receive ETH for WETH");
|
||||
}
|
||||
|
||||
/// @notice Test burn to a different receiver with native output
|
||||
function testBurnToReceiverWithNativeOutput() public {
|
||||
uint256 lpToBurn = pool.totalSupply() / 10;
|
||||
uint256[] memory withdraws = viewer.burnAmounts(pool, lpToBurn);
|
||||
|
||||
uint256 bobEthBefore = bob.balance;
|
||||
uint256 bobToken0Before = token0.balanceOf(bob);
|
||||
uint256 bobToken1Before = token1.balanceOf(bob);
|
||||
|
||||
// Burn LP and send to bob with unwrap=true
|
||||
pool.burn(
|
||||
address(this), // payer
|
||||
bob, // receiver
|
||||
lpToBurn,
|
||||
0,
|
||||
true // unwrap
|
||||
);
|
||||
|
||||
// Bob should receive tokens and native currency
|
||||
assertEq(token0.balanceOf(bob), bobToken0Before + withdraws[0], "Bob token0");
|
||||
assertEq(token1.balanceOf(bob), bobToken1Before + withdraws[1], "Bob token1");
|
||||
assertEq(bob.balance, bobEthBefore + withdraws[2], "Bob should receive ETH");
|
||||
}
|
||||
|
||||
/* ----------------------
|
||||
SwapMint Tests with Native Currency
|
||||
---------------------- */
|
||||
|
||||
/// @notice Test swapMint with native currency input
|
||||
function testSwapMintWithNativeInput() public {
|
||||
uint256 maxIn = 10_000;
|
||||
|
||||
vm.startPrank(alice);
|
||||
|
||||
uint256 aliceEthBefore = alice.balance;
|
||||
uint256 aliceLpBefore = pool.balanceOf(alice);
|
||||
|
||||
// Call swapMint with native currency: deposit ETH as WETH (index 2)
|
||||
uint256 lpMinted = pool.swapMint{value: maxIn}(
|
||||
alice, // payer
|
||||
alice, // receiver
|
||||
2, // inputTokenIndex (WETH)
|
||||
maxIn, // maxAmountIn
|
||||
0 // deadline
|
||||
);
|
||||
|
||||
assertTrue(lpMinted > 0, "swapMint should mint LP");
|
||||
|
||||
// Alice's ETH should decrease (by at most maxIn)
|
||||
assertTrue(alice.balance <= aliceEthBefore, "Alice ETH should decrease");
|
||||
assertTrue(aliceEthBefore - alice.balance <= maxIn, "Alice spent at most maxIn");
|
||||
|
||||
// Alice should receive LP tokens
|
||||
assertTrue(pool.balanceOf(alice) >= aliceLpBefore + lpMinted, "Alice should receive LP");
|
||||
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
/// @notice Test swapMint with excess native currency - verify refund
|
||||
function testSwapMintWithExcessNativeRefunded() public {
|
||||
uint256 maxIn = 10_000;
|
||||
uint256 excess = 20_000;
|
||||
uint256 totalSent = maxIn + excess;
|
||||
|
||||
vm.startPrank(alice);
|
||||
|
||||
uint256 aliceEthBefore = alice.balance;
|
||||
|
||||
// Send excess native currency
|
||||
uint256 lpMinted = pool.swapMint{value: totalSent}(
|
||||
alice,
|
||||
alice,
|
||||
2, // WETH
|
||||
maxIn,
|
||||
0
|
||||
);
|
||||
|
||||
assertTrue(lpMinted > 0, "swapMint should mint LP");
|
||||
|
||||
// Alice should not lose more than maxIn
|
||||
assertTrue(aliceEthBefore - alice.balance <= maxIn, "Alice should be refunded excess");
|
||||
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
/* ----------------------
|
||||
BurnSwap Tests with Native Currency
|
||||
---------------------- */
|
||||
|
||||
/// @notice Test burnSwap with native currency output (unwrap=true)
|
||||
function testBurnSwapWithNativeOutput() public {
|
||||
uint256 lpToBurn = pool.totalSupply() / 10;
|
||||
|
||||
uint256 thisEthBefore = address(this).balance;
|
||||
|
||||
// Burn LP and receive all proceeds as native currency (WETH unwrapped)
|
||||
uint256 payout = pool.burnSwap(
|
||||
address(this), // payer (holds LP)
|
||||
address(this), // receiver
|
||||
lpToBurn, // lpAmount
|
||||
2, // inputTokenIndex (WETH)
|
||||
0, // deadline
|
||||
true // unwrap (receive native currency)
|
||||
);
|
||||
|
||||
assertTrue(payout > 0, "burnSwap should produce payout");
|
||||
|
||||
// This contract should receive native currency
|
||||
assertEq(address(this).balance, thisEthBefore + payout, "Should receive ETH");
|
||||
}
|
||||
|
||||
/// @notice Test burnSwap to different receiver with native output
|
||||
function testBurnSwapToReceiverWithNativeOutput() public {
|
||||
uint256 lpToBurn = pool.totalSupply() / 10;
|
||||
|
||||
uint256 bobEthBefore = bob.balance;
|
||||
|
||||
// Burn LP and send native currency to bob
|
||||
uint256 payout = pool.burnSwap(
|
||||
address(this), // payer
|
||||
bob, // receiver
|
||||
lpToBurn,
|
||||
2, // WETH
|
||||
0,
|
||||
true // unwrap
|
||||
);
|
||||
|
||||
assertTrue(payout > 0, "burnSwap should produce payout");
|
||||
|
||||
// Bob should receive native currency
|
||||
assertEq(bob.balance, bobEthBefore + payout, "Bob should receive ETH");
|
||||
}
|
||||
|
||||
/* ----------------------
|
||||
Combined Native Operations
|
||||
---------------------- */
|
||||
|
||||
/// @notice Test full cycle: mint with native -> swap with native -> burn with native
|
||||
function testFullCycleWithNative() public {
|
||||
vm.startPrank(alice);
|
||||
|
||||
// 1. Mint with native currency
|
||||
uint256 lpRequest = pool.totalSupply() / 20; // 5% of pool
|
||||
uint256[] memory deposits = viewer.mintAmounts(pool, lpRequest);
|
||||
|
||||
token0.approve(address(pool), type(uint256).max);
|
||||
token1.approve(address(pool), type(uint256).max);
|
||||
|
||||
uint256 aliceEthStart = alice.balance;
|
||||
|
||||
uint256 lpMinted = pool.mint{value: deposits[2]}(alice, alice, lpRequest, 0);
|
||||
assertTrue(lpMinted > 0, "Should mint LP");
|
||||
|
||||
// 2. Swap native currency for token0
|
||||
uint256 swapAmount = 5_000;
|
||||
(uint256 amountIn, uint256 amountOut, ) = pool.swap{value: swapAmount}(
|
||||
alice, alice, 2, 0, swapAmount, 0, 0, false
|
||||
);
|
||||
assertTrue(amountOut > 0, "Should receive token0");
|
||||
|
||||
// 3. Swap token0 back to native currency
|
||||
uint256 token0Balance = token0.balanceOf(alice);
|
||||
(uint256 swapIn2, uint256 swapOut2, ) = pool.swap(
|
||||
alice, alice, 0, 2, token0Balance / 2, 0, 0, true
|
||||
);
|
||||
assertTrue(swapOut2 > 0, "Should receive native currency");
|
||||
|
||||
// 4. Burn LP to native currency
|
||||
uint256 lpToBurn = lpMinted / 2;
|
||||
uint256 payout = pool.burnSwap(alice, alice, lpToBurn, 2, 0, true);
|
||||
assertTrue(payout > 0, "Should receive payout in native");
|
||||
|
||||
// Alice should have some ETH back (maybe more or less depending on slippage)
|
||||
assertTrue(alice.balance > 0, "Alice should have some ETH");
|
||||
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
/// @notice Test that unwrap=false with WETH actually transfers WETH tokens (not native)
|
||||
function testSwapWithWethNoUnwrap() public {
|
||||
uint256 maxIn = 10_000;
|
||||
|
||||
vm.startPrank(alice);
|
||||
token0.approve(address(pool), type(uint256).max);
|
||||
|
||||
uint256 aliceWethBefore = weth.balanceOf(alice);
|
||||
uint256 aliceEthBefore = alice.balance;
|
||||
|
||||
// Swap token0 -> WETH without unwrap
|
||||
(uint256 amountIn, uint256 amountOut, ) = pool.swap(
|
||||
alice, alice, 0, 2, maxIn, 0, 0, false // unwrap=false
|
||||
);
|
||||
|
||||
assertTrue(amountOut > 0, "Should receive WETH tokens");
|
||||
|
||||
// Alice's WETH balance should increase
|
||||
assertEq(weth.balanceOf(alice), aliceWethBefore + amountOut, "Alice should receive WETH tokens");
|
||||
|
||||
// Alice's ETH balance should not change (except for gas, but we don't track that)
|
||||
assertEq(alice.balance, aliceEthBefore, "Alice ETH should not change with unwrap=false");
|
||||
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
/// @notice Verify that sending native currency for non-WETH input reverts
|
||||
function testSwapNativeForNonWethReverts() public {
|
||||
vm.startPrank(alice);
|
||||
|
||||
// Try to swap token0 (not WETH) by sending native currency - should revert
|
||||
vm.expectRevert();
|
||||
pool.swap{value: 10_000}(
|
||||
alice, alice, 0, 1, 10_000, 0, 0, false
|
||||
);
|
||||
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
// Make this contract payable to receive native currency from pool
|
||||
receive() external payable {}
|
||||
}
|
||||
/* solhint-enable */
|
||||
Reference in New Issue
Block a user