diff --git a/evm/src/balancer-v2/BalancerV2SwapAdapter.sol b/evm/src/balancer-v2/BalancerV2SwapAdapter.sol index f545269..9cbf25c 100644 --- a/evm/src/balancer-v2/BalancerV2SwapAdapter.sol +++ b/evm/src/balancer-v2/BalancerV2SwapAdapter.sol @@ -44,9 +44,43 @@ contract BalancerV2SwapAdapter is ISwapAdapter { IVault.SwapKind.GIVEN_IN, swapSteps, assets, funds ); + // TODO: the delta of buyToken is negative, so we need to flip the sign calculatedPrice = Fraction(uint256(assetDeltas[1]), sellAmount); } + function getSellAmount( + bytes32 pairId, + IERC20 sellToken, + IERC20 buyToken, + uint256 buyAmount + ) public returns (uint256 sellAmount) { + IVault.BatchSwapStep[] memory swapSteps = new IVault.BatchSwapStep[](1); + swapSteps[0] = IVault.BatchSwapStep({ + poolId: pairId, + assetInIndex: 0, + assetOutIndex: 1, + amount: buyAmount, + userData: "" + }); + address[] memory assets = new address[](2); + assets[0] = address(sellToken); + assets[1] = address(buyToken); + IVault.FundManagement memory funds = IVault.FundManagement({ + sender: msg.sender, + fromInternalBalance: false, + recipient: payable(msg.sender), + toInternalBalance: false + }); + + // assetDeltas correspond to the assets array + int256[] memory assetDeltas = new int256[](2); + assetDeltas = vault.queryBatchSwap( + IVault.SwapKind.GIVEN_OUT, swapSteps, assets, funds + ); + + sellAmount = uint256(assetDeltas[0]); + } + function priceBatch( bytes32 pairId, IERC20 sellToken, @@ -75,31 +109,39 @@ contract BalancerV2SwapAdapter is ISwapAdapter { OrderSide side, uint256 specifiedAmount ) external override returns (Trade memory trade) { + uint256 sellAmount; + IVault.SwapKind kind; + uint256 limit; // TODO set this slippage limit properly if (side == OrderSide.Sell) { - sellToken.approve(address(vault), specifiedAmount); + kind = IVault.SwapKind.GIVEN_IN; + sellAmount = specifiedAmount; + limit = 0; } else { - buyToken.approve(address(vault), type(uint256).max); + kind = IVault.SwapKind.GIVEN_OUT; + sellAmount = getSellAmount(pairId, sellToken, buyToken, specifiedAmount); + limit = type(uint256).max; } + + sellToken.transferFrom(msg.sender, address(this), sellAmount); + sellToken.approve(address(vault), sellAmount); + uint256 gasBefore = gasleft(); - trade.receivedAmount = vault.swap( + trade.calculatedAmount = vault.swap( IVault.SingleSwap({ poolId: pairId, - kind: side == OrderSide.Sell - ? IVault.SwapKind.GIVEN_IN - : IVault.SwapKind.GIVEN_OUT, + kind: kind, assetIn: address(sellToken), assetOut: address(buyToken), amount: specifiedAmount, userData: "" }), - // This contract is not an approved relayer (yet), so the sender and recipient cannot be msg.sender IVault.FundManagement({ sender: address(this), fromInternalBalance: false, - recipient: payable(address(this)), + recipient: msg.sender, toInternalBalance: false }), - 0, + limit, block.timestamp + SWAP_DEADLINE_SEC ); trade.gasUsed = gasBefore - gasleft(); diff --git a/evm/src/interfaces/ISwapAdapterTypes.sol b/evm/src/interfaces/ISwapAdapterTypes.sol index 0fa0f84..81767e0 100644 --- a/evm/src/interfaces/ISwapAdapterTypes.sol +++ b/evm/src/interfaces/ISwapAdapterTypes.sol @@ -46,7 +46,7 @@ interface ISwapAdapterTypes { struct Trade { // If the side is sell, it is the amount of tokens sold. If the side is // buy, it is the amount of tokens bought. - uint256 receivedAmount; + uint256 calculatedAmount; // The amount of gas used in the trade. uint256 gasUsed; // The price of the pair after the trade. For zero use Fraction(0, 1). diff --git a/evm/src/uniswap-v2/UniswapV2SwapAdapter.sol b/evm/src/uniswap-v2/UniswapV2SwapAdapter.sol index 9329bc8..a413947 100644 --- a/evm/src/uniswap-v2/UniswapV2SwapAdapter.sol +++ b/evm/src/uniswap-v2/UniswapV2SwapAdapter.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.13; import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; -uint256 constant RESERVE_LIMIT_FACTOR = 10; // TODO why is the factor so high? +uint256 constant RESERVE_LIMIT_FACTOR = 2; // TODO why is the factor so high? contract UniswapV2SwapAdapter is ISwapAdapter { IUniswapV2Factory immutable factory; @@ -73,10 +73,10 @@ contract UniswapV2SwapAdapter is ISwapAdapter { } uint256 gasBefore = gasleft(); if (side == OrderSide.Sell) { - trade.receivedAmount = + trade.calculatedAmount = sell(pair, sellToken, zero2one, r0, r1, specifiedAmount); } else { - trade.receivedAmount = + trade.calculatedAmount = buy(pair, sellToken, zero2one, r0, r1, specifiedAmount); } trade.gasUsed = gasBefore - gasleft(); @@ -90,11 +90,12 @@ contract UniswapV2SwapAdapter is ISwapAdapter { uint112 reserveIn, uint112 reserveOut, uint256 amount - ) internal returns (uint256 receivedAmount) { + ) internal returns (uint256 calculatedAmount) { address swapper = msg.sender; + uint256 amountOut = getAmountOut(amount, reserveIn, reserveOut); + // TODO: use safeTransferFrom sellToken.transferFrom(swapper, address(pair), amount); - uint256 amountOut = getAmountOut(amount, reserveIn, reserveOut); if (zero2one) { pair.swap(0, amountOut, swapper, ""); } else { @@ -129,9 +130,10 @@ contract UniswapV2SwapAdapter is ISwapAdapter { uint112 reserveIn, uint112 reserveOut, uint256 amountOut - ) internal returns (uint256 receivedAmount) { + ) internal returns (uint256 calculatedAmount) { address swapper = msg.sender; uint256 amount = getAmountIn(amountOut, reserveIn, reserveOut); + if (amount == 0) { return 0; } @@ -174,10 +176,13 @@ contract UniswapV2SwapAdapter is ISwapAdapter { { IUniswapV2Pair pair = IUniswapV2Pair(address(bytes20(pairId))); limits = new uint256[](2); + (uint256 r0, uint256 r1,) = pair.getReserves(); if (sellToken < buyToken) { - (limits[0], limits[1],) = pair.getReserves(); + limits[0] = r0 / RESERVE_LIMIT_FACTOR; + limits[1] = r1 / RESERVE_LIMIT_FACTOR; } else { - (limits[1], limits[0],) = pair.getReserves(); + limits[0] = r1 / RESERVE_LIMIT_FACTOR; + limits[1] = r0 / RESERVE_LIMIT_FACTOR; } } diff --git a/evm/test/BalancerV2SwapAdapter.t.sol b/evm/test/BalancerV2SwapAdapter.t.sol index 491bde5..4aa1e08 100644 --- a/evm/test/BalancerV2SwapAdapter.t.sol +++ b/evm/test/BalancerV2SwapAdapter.t.sol @@ -21,7 +21,7 @@ contract BalancerV2SwapAdapterTest is Test, ISwapAdapterTypes { 0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014; function setUp() public { - uint256 forkBlock = 17000000; + uint256 forkBlock = 18710000; vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); adapter = new BalancerV2SwapAdapter(payable(address(balancerV2Vault))); @@ -62,19 +62,40 @@ contract BalancerV2SwapAdapterTest is Test, ISwapAdapterTypes { // assertGt(price.numerator, 0); // } - function testSwapFuzz() public { - // uint256[] memory limits = adapter.getLimits(B_80BAL_20WETH_POOL_ID, BAL, WETH); - // vm.assume(amount < limits[0]); - // vm.assume(amount > 1000000); // TODO getting reverts for amounts near zero - uint256 amount = 100000; + function testSwapFuzz(uint256 specifiedAmount, bool isBuy) public { + OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell; + vm.assume(specifiedAmount > 0); - OrderSide side = OrderSide.Sell; + uint256[] memory limits = adapter.getLimits(B_80BAL_20WETH_POOL_ID, BAL, WETH); - deal(address(BAL), address(adapter), amount); - // BAL.approve(address(adapter), amount); - // BAL.approve(address(balancerV2Vault), amount); + if (side == OrderSide.Buy) { + vm.assume(specifiedAmount < limits[1]); - adapter.swap(B_80BAL_20WETH_POOL_ID, BAL, WETH, side, amount); + // sellAmount is not specified for buy orders + deal(address(BAL), address(this), type(uint256).max); + BAL.approve(address(adapter), type(uint256).max); + } + else { + vm.assume(specifiedAmount < limits[0]); + + deal(address(BAL), address(this), specifiedAmount); + BAL.approve(address(adapter), specifiedAmount); + } + + uint256 bal_balance = BAL.balanceOf(address(this)); + uint256 weth_balance = WETH.balanceOf(address(this)); + + Trade memory trade = adapter.swap(B_80BAL_20WETH_POOL_ID, BAL, WETH, side, specifiedAmount); + + if (trade.calculatedAmount > 0) { + if (side == OrderSide.Buy) { + assertEq(specifiedAmount, WETH.balanceOf(address(this)) - weth_balance); + assertEq(trade.calculatedAmount, bal_balance - BAL.balanceOf(address(this))); + } else { + assertEq(specifiedAmount, bal_balance - BAL.balanceOf(address(this))); + assertEq(trade.calculatedAmount, WETH.balanceOf(address(this)) - weth_balance); + } + } } function testGetLimits() public view { diff --git a/evm/test/UniswapV2SwapAdapter.t.sol b/evm/test/UniswapV2SwapAdapter.t.sol index 9a89e4f..c160dc7 100644 --- a/evm/test/UniswapV2SwapAdapter.t.sol +++ b/evm/test/UniswapV2SwapAdapter.t.sol @@ -17,6 +17,11 @@ contract UniswapV2PairFunctionTest is Test, ISwapAdapterTypes { vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); pairFunctions = new UniswapV2SwapAdapter(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f); + + vm.label(address(pairFunctions), "UniswapV2SwapAdapter"); + vm.label(address(WETH), "WETH"); + vm.label(address(USDC), "USDC"); + vm.label(address(USDC_WETH_PAIR), "USDC_WETH_PAIR"); } function testPriceFuzz(uint256 amount0, uint256 amount1) public { @@ -72,17 +77,40 @@ contract UniswapV2PairFunctionTest is Test, ISwapAdapterTypes { else return -1; } - function testSwapFuzz(uint256 amount, bool isBuy) public { - bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); + function testSwapFuzz(uint256 specifiedAmount, bool isBuy) public { OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell; + bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); uint256[] memory limits = pairFunctions.getLimits(pair, USDC, WETH); - vm.assume(amount < limits[0]); - - deal(address(USDC), address(this), amount); - USDC.approve(address(pairFunctions), amount); - pairFunctions.swap(pair, USDC, WETH, side, amount); + if (side == OrderSide.Buy) { + vm.assume(specifiedAmount < limits[1]); + + // sellAmount is not specified for buy orders + deal(address(USDC), address(this), type(uint256).max); + USDC.approve(address(pairFunctions), type(uint256).max); + } + else { + vm.assume(specifiedAmount < limits[0]); + + deal(address(USDC), address(this), specifiedAmount); + USDC.approve(address(pairFunctions), specifiedAmount); + } + + uint256 usdc_balance = USDC.balanceOf(address(this)); + uint256 weth_balance = WETH.balanceOf(address(this)); + + Trade memory trade = pairFunctions.swap(pair, USDC, WETH, side, specifiedAmount); + + if (trade.calculatedAmount > 0) { + if (side == OrderSide.Buy) { + assertEq(specifiedAmount, WETH.balanceOf(address(this)) - weth_balance); + assertEq(trade.calculatedAmount, usdc_balance - USDC.balanceOf(address(this))); + } else { + assertEq(specifiedAmount, usdc_balance - USDC.balanceOf(address(this))); + assertEq(trade.calculatedAmount, WETH.balanceOf(address(this)) - weth_balance); + } + } } function testSwapSellIncreasing() public { @@ -91,24 +119,27 @@ contract UniswapV2PairFunctionTest is Test, ISwapAdapterTypes { function executeIncreasingSwaps(OrderSide side) internal { bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); + uint256 iterations = 100; - uint256[] memory amounts = new uint256[](100); + uint256[] memory amounts = new uint256[](iterations); for (uint256 i = 0; i < 100; i++) { amounts[i] = 1000 * i * 10 ** 6; } - Trade[] memory trades = new Trade [](100); + Trade[] memory trades = new Trade[](iterations); uint256 beforeSwap; - for (uint256 i = 0; i < 100; i++) { + for (uint256 i = 0; i < iterations; i++) { beforeSwap = vm.snapshot(); + deal(address(USDC), address(this), amounts[i]); USDC.approve(address(pairFunctions), amounts[i]); + trades[i] = pairFunctions.swap(pair, USDC, WETH, side, amounts[i]); vm.revertTo(beforeSwap); } - for (uint256 i = 1; i < 99; i++) { - assertLe(trades[i].receivedAmount, trades[i + 1].receivedAmount); + for (uint256 i = 1; i < iterations - 1; i++) { + assertLe(trades[i].calculatedAmount, trades[i + 1].calculatedAmount); assertLe(trades[i].gasUsed, trades[i + 1].gasUsed); assertEq(compareFractions(trades[i].price, trades[i + 1].price), 1); }