Skip to content

Commit 672c1ba

Browse files
chore: migrate E2E uniswapTrade test to Foundry (#222)
## Description Migrate the uniswapTrade e2e test to foundry. Depends on #215. ## Test Plan CI ## Related Issues Closes #141
1 parent fbfd19f commit 672c1ba

File tree

2 files changed

+195
-206
lines changed

2 files changed

+195
-206
lines changed

test/e2e/UniswapTrade.t.sol

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
pragma solidity ^0.8;
3+
4+
import {Vm} from "forge-std/Vm.sol";
5+
6+
import {IERC20} from "src/contracts/interfaces/IERC20.sol";
7+
8+
import {GPv2Interaction} from "src/contracts/libraries/GPv2Interaction.sol";
9+
import {GPv2Order} from "src/contracts/libraries/GPv2Order.sol";
10+
import {GPv2Signing} from "src/contracts/mixins/GPv2Signing.sol";
11+
12+
import {SettlementEncoder} from "../libraries/encoders/SettlementEncoder.sol";
13+
import {Registry, TokenRegistry} from "../libraries/encoders/TokenRegistry.sol";
14+
import {Helper, IERC20Mintable} from "./Helper.sol";
15+
16+
using SettlementEncoder for SettlementEncoder.State;
17+
using TokenRegistry for TokenRegistry.State;
18+
using TokenRegistry for Registry;
19+
20+
interface IUniswapV2Factory {
21+
function createPair(address, address) external returns (address);
22+
}
23+
24+
interface IUniswapV2Pair {
25+
function token0() external view returns (address);
26+
function mint(address) external;
27+
function swap(uint256, uint256, address, bytes calldata) external;
28+
function getReserves() external view returns (uint256, uint256);
29+
}
30+
31+
contract UniswapTradeTest is Helper(true) {
32+
IERC20Mintable dai;
33+
IERC20Mintable wETH;
34+
35+
IUniswapV2Factory factory;
36+
IUniswapV2Pair uniswapPair;
37+
38+
bool isWethToken0;
39+
40+
function setUp() public override {
41+
super.setUp();
42+
43+
dai = deployMintableErc20("dai", "dai");
44+
wETH = deployMintableErc20("wETH", "wETH");
45+
46+
factory = IUniswapV2Factory(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f);
47+
uniswapPair = IUniswapV2Pair(factory.createPair(address(wETH), address(dai)));
48+
49+
isWethToken0 = uniswapPair.token0() == address(wETH);
50+
}
51+
52+
// Settles the following batch:
53+
//
54+
// /----(1. SELL 1 wETH for dai if p(wETH) >= 500)-----\
55+
// | |
56+
// | v
57+
// [dai]<---(Uniswap Pair 1000 wETH / 600.000 dai)--->[wETH]
58+
// ^ |
59+
// | |
60+
// \----(2. BUY 0.5 wETH for dai if p(wETH) <= 600)----/
61+
function test_should_two_overlapping_orders_and_trade_surplus_with_uniswap() external {
62+
uint256 wethReserve = 1000 ether;
63+
uint256 daiReserve = 600000 ether;
64+
wETH.mint(address(uniswapPair), wethReserve);
65+
dai.mint(address(uniswapPair), daiReserve);
66+
uniswapPair.mint(address(this));
67+
68+
// The current batch has a sell order selling 1 wETH and a buy order buying
69+
// 0.5 wETH. This means there is exactly a surplus 0.5 wETH that needs to be
70+
// sold to Uniswap. Uniswap is governed by a balancing equation which can be
71+
// used to compute the exact buy amount for selling the 0.5 wETH and we can
72+
// use to build our the settlement with a smart contract interaction.
73+
// ```
74+
// (reservewETH + inwETH * 0.997) * (reservedai - outdai) = reservewETH * reservedai
75+
// outdai = (reservedai * inwETH * 0.997) / (reservewETH + inwETH * 0.997)
76+
// = (reservedai * inwETH * 997) / (reservewETH * 1000 + inwETH * 997)
77+
// ```
78+
uint256 uniswapWethInAmount = 0.5 ether;
79+
uint256 uniswapDaiOutAmount =
80+
daiReserve * uniswapWethInAmount * 997 / ((wethReserve * 1000) + (uniswapWethInAmount * 997));
81+
82+
Vm.Wallet memory trader1 = vm.createWallet("trader1");
83+
Vm.Wallet memory trader2 = vm.createWallet("trader2");
84+
85+
// mint some weth
86+
wETH.mint(trader1.addr, 1.001 ether);
87+
vm.prank(trader1.addr);
88+
wETH.approve(vaultRelayer, type(uint256).max);
89+
90+
// place order to sell 1 wETH for min 500 dai
91+
encoder.signEncodeTrade(
92+
vm,
93+
trader1,
94+
GPv2Order.Data({
95+
kind: GPv2Order.KIND_SELL,
96+
partiallyFillable: false,
97+
sellToken: wETH,
98+
buyToken: dai,
99+
sellAmount: 1 ether,
100+
buyAmount: 500 ether,
101+
feeAmount: 0.001 ether,
102+
validTo: 0xffffffff,
103+
appData: bytes32(uint256(1)),
104+
sellTokenBalance: GPv2Order.BALANCE_ERC20,
105+
buyTokenBalance: GPv2Order.BALANCE_ERC20,
106+
receiver: GPv2Order.RECEIVER_SAME_AS_OWNER
107+
}),
108+
domainSeparator,
109+
GPv2Signing.Scheme.Eip712,
110+
0
111+
);
112+
113+
// mint some dai
114+
dai.mint(trader2.addr, 300.3 ether);
115+
vm.prank(trader2.addr);
116+
dai.approve(vaultRelayer, type(uint256).max);
117+
118+
// place order to buy 0.5 wETH for max 300 dai
119+
encoder.signEncodeTrade(
120+
vm,
121+
trader2,
122+
GPv2Order.Data({
123+
kind: GPv2Order.KIND_BUY,
124+
partiallyFillable: false,
125+
sellToken: dai,
126+
buyToken: wETH,
127+
sellAmount: 300 ether,
128+
buyAmount: 0.5 ether,
129+
feeAmount: 0.3 ether,
130+
validTo: 0xffffffff,
131+
appData: bytes32(uint256(1)),
132+
sellTokenBalance: GPv2Order.BALANCE_ERC20,
133+
buyTokenBalance: GPv2Order.BALANCE_ERC20,
134+
receiver: GPv2Order.RECEIVER_SAME_AS_OWNER
135+
}),
136+
domainSeparator,
137+
GPv2Signing.Scheme.Eip712,
138+
0
139+
);
140+
141+
// interaction to swap the remainder on uniswap
142+
encoder.addInteraction(
143+
GPv2Interaction.Data({
144+
target: address(wETH),
145+
value: 0,
146+
callData: abi.encodeCall(IERC20.transfer, (address(uniswapPair), uniswapWethInAmount))
147+
}),
148+
SettlementEncoder.InteractionStage.INTRA
149+
);
150+
(uint256 amount0Out, uint256 amount1Out) =
151+
isWethToken0 ? (uint256(0), uniswapDaiOutAmount) : (uniswapDaiOutAmount, uint256(0));
152+
encoder.addInteraction(
153+
GPv2Interaction.Data({
154+
target: address(uniswapPair),
155+
value: 0,
156+
callData: abi.encodeCall(IUniswapV2Pair.swap, (amount0Out, amount1Out, address(settlement), hex""))
157+
}),
158+
SettlementEncoder.InteractionStage.INTRA
159+
);
160+
161+
// set token prices
162+
IERC20[] memory tokens = new IERC20[](2);
163+
tokens[0] = wETH;
164+
tokens[1] = dai;
165+
uint256[] memory prices = new uint256[](2);
166+
prices[0] = uniswapDaiOutAmount;
167+
prices[1] = uniswapWethInAmount;
168+
encoder.tokenRegistry.tokenRegistry().setPrices(tokens, prices);
169+
170+
SettlementEncoder.EncodedSettlement memory encodedSettlement = encoder.encode(settlement);
171+
172+
vm.prank(solver);
173+
settle(encodedSettlement);
174+
175+
assertEq(wETH.balanceOf(address(settlement)), 0.001 ether, "weth fees not as expected");
176+
assertEq(dai.balanceOf(address(settlement)), 0.3 ether, "dai fees not as expected");
177+
178+
assertEq(wETH.balanceOf(trader1.addr), 0, "not all weth sold");
179+
assertEq(dai.balanceOf(trader1.addr), uniswapDaiOutAmount * 2, "dai received not as expected");
180+
181+
assertEq(wETH.balanceOf(trader2.addr), 0.5 ether, "weth bought not correct amount");
182+
assertEq(dai.balanceOf(trader2.addr), 300.3 ether - (uniswapDaiOutAmount + 0.3 ether));
183+
184+
uint256 finalWethReserve;
185+
uint256 finalDaiReserve;
186+
187+
{
188+
(uint256 token0Reserve, uint256 token1Reserve) = uniswapPair.getReserves();
189+
(finalWethReserve, finalDaiReserve) =
190+
isWethToken0 ? (token0Reserve, token1Reserve) : (token1Reserve, token0Reserve);
191+
}
192+
assertEq(finalWethReserve, wethReserve + uniswapWethInAmount, "weth reserve not as expected");
193+
assertEq(finalDaiReserve, daiReserve - uniswapDaiOutAmount, "dai reserve not as expected");
194+
}
195+
}

0 commit comments

Comments
 (0)