SKIP TO CONTENT

Demo build — mock protocol data. No chain, no ABIs yet.

  • SYNCING: ……

MockUniV2.sol

Uniswap V2 pair stub for the TWAP oracle and pair-fee tests.

205 lines7.9 KBSolidity
Source of test/mocks/MockUniV2.sol, 205 lines of Solidity
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.24;
3 
4interface IMockERC20 {
5 function balanceOf(address) external view returns (uint256);
6 function transfer(address, uint256) external returns (bool);
7 function transferFrom(address, address, uint256) external returns (bool);
8}
9 
10/**
11 * @dev Minimal Uniswap V2 pair: constant product, 0.30% fee, and — importantly for the
12 * oracle tests — real `price0CumulativeLast` / `price1CumulativeLast` accumulation
13 * in UQ112x112, updated exactly like the real pair does in `_update`.
14 */
15contract MockUniV2Pair {
16 address public token0;
17 address public token1;
18 
19 uint112 private reserve0;
20 uint112 private reserve1;
21 uint32 private blockTimestampLast;
22 
23 uint256 public price0CumulativeLast;
24 uint256 public price1CumulativeLast;
25 
26 uint256 public totalSupply;
27 mapping(address => uint256) public balanceOf;
28 mapping(address => mapping(address => uint256)) public allowance;
29 
30 string public constant name = "Mock LP";
31 string public constant symbol = "MLP";
32 uint8 public constant decimals = 18;
33 
34 event Transfer(address indexed from, address indexed to, uint256 value);
35 event Approval(address indexed owner, address indexed spender, uint256 value);
36 event Sync(uint112 reserve0, uint112 reserve1);
37 
38 constructor(address t0, address t1) {
39 (token0, token1) = t0 < t1 ? (t0, t1) : (t1, t0);
40 }
41 
42 function getReserves() public view returns (uint112, uint112, uint32) {
43 return (reserve0, reserve1, blockTimestampLast);
44 }
45 
46 function _update(uint256 bal0, uint256 bal1) private {
47 uint32 blockTimestamp = uint32(block.timestamp % 2 ** 32);
48 uint32 timeElapsed;
49 unchecked {
50 timeElapsed = blockTimestamp - blockTimestampLast;
51 }
52 if (timeElapsed > 0 && reserve0 != 0 && reserve1 != 0) {
53 unchecked {
54 price0CumulativeLast += ((uint256(reserve1) << 112) / reserve0) * timeElapsed;
55 price1CumulativeLast += ((uint256(reserve0) << 112) / reserve1) * timeElapsed;
56 }
57 }
58 reserve0 = uint112(bal0);
59 reserve1 = uint112(bal1);
60 blockTimestampLast = blockTimestamp;
61 emit Sync(reserve0, reserve1);
62 }
63 
64 function sync() external {
65 _update(IMockERC20(token0).balanceOf(address(this)), IMockERC20(token1).balanceOf(address(this)));
66 }
67 
68 function mint(address to) external returns (uint256 liquidity) {
69 uint256 bal0 = IMockERC20(token0).balanceOf(address(this));
70 uint256 bal1 = IMockERC20(token1).balanceOf(address(this));
71 uint256 amount0 = bal0 - reserve0;
72 uint256 amount1 = bal1 - reserve1;
73 
74 if (totalSupply == 0) {
75 liquidity = _sqrt(amount0 * amount1);
76 } else {
77 uint256 a = (amount0 * totalSupply) / reserve0;
78 uint256 b = (amount1 * totalSupply) / reserve1;
79 liquidity = a < b ? a : b;
80 }
81 require(liquidity > 0, "MockPair: insufficient liquidity minted");
82 totalSupply += liquidity;
83 balanceOf[to] += liquidity;
84 emit Transfer(address(0), to, liquidity);
85 _update(bal0, bal1);
86 }
87 
88 function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata) external {
89 require(amount0Out > 0 || amount1Out > 0, "MockPair: zero output");
90 (uint112 r0, uint112 r1,) = getReserves();
91 require(amount0Out < r0 && amount1Out < r1, "MockPair: insufficient liquidity");
92 
93 if (amount0Out > 0) IMockERC20(token0).transfer(to, amount0Out);
94 if (amount1Out > 0) IMockERC20(token1).transfer(to, amount1Out);
95 
96 uint256 bal0 = IMockERC20(token0).balanceOf(address(this));
97 uint256 bal1 = IMockERC20(token1).balanceOf(address(this));
98 uint256 amount0In = bal0 > r0 - amount0Out ? bal0 - (r0 - amount0Out) : 0;
99 uint256 amount1In = bal1 > r1 - amount1Out ? bal1 - (r1 - amount1Out) : 0;
100 require(amount0In > 0 || amount1In > 0, "MockPair: insufficient input");
101 
102 uint256 bal0Adj = bal0 * 1000 - amount0In * 3;
103 uint256 bal1Adj = bal1 * 1000 - amount1In * 3;
104 require(bal0Adj * bal1Adj >= uint256(r0) * uint256(r1) * 1000 ** 2, "MockPair: K");
105 
106 _update(bal0, bal1);
107 }
108 
109 function transfer(address to, uint256 value) external returns (bool) {
110 balanceOf[msg.sender] -= value;
111 balanceOf[to] += value;
112 emit Transfer(msg.sender, to, value);
113 return true;
114 }
115 
116 function approve(address spender, uint256 value) external returns (bool) {
117 allowance[msg.sender][spender] = value;
118 emit Approval(msg.sender, spender, value);
119 return true;
120 }
121 
122 function transferFrom(address from, address to, uint256 value) external returns (bool) {
123 uint256 allowed = allowance[from][msg.sender];
124 if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - value;
125 balanceOf[from] -= value;
126 balanceOf[to] += value;
127 emit Transfer(from, to, value);
128 return true;
129 }
130 
131 function _sqrt(uint256 y) private pure returns (uint256 z) {
132 if (y > 3) {
133 z = y;
134 uint256 x = y / 2 + 1;
135 while (x < z) {
136 z = x;
137 x = (y / x + x) / 2;
138 }
139 } else if (y != 0) {
140 z = 1;
141 }
142 }
143}
144 
145/// @dev Minimal Uniswap V2 router over an explicit pair registry.
146contract MockUniV2Router {
147 mapping(address => mapping(address => address)) public pairFor;
148 
149 function setPair(address tokenA, address tokenB, address pair) external {
150 pairFor[tokenA][tokenB] = pair;
151 pairFor[tokenB][tokenA] = pair;
152 }
153 
154 function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) public pure returns (uint256) {
155 uint256 amountInWithFee = amountIn * 997;
156 return (amountInWithFee * reserveOut) / (reserveIn * 1000 + amountInWithFee);
157 }
158 
159 function getAmountsOut(uint256 amountIn, address[] calldata path) external view returns (uint256[] memory amounts) {
160 amounts = new uint256[](path.length);
161 amounts[0] = amountIn;
162 for (uint256 i; i + 1 < path.length; ++i) {
163 (uint256 rIn, uint256 rOut) = _reserves(path[i], path[i + 1]);
164 amounts[i + 1] = getAmountOut(amounts[i], rIn, rOut);
165 }
166 }
167 
168 function _reserves(address tokenIn, address tokenOut) internal view returns (uint256 rIn, uint256 rOut) {
169 MockUniV2Pair pair = MockUniV2Pair(pairFor[tokenIn][tokenOut]);
170 (uint112 r0, uint112 r1,) = pair.getReserves();
171 return pair.token0() == tokenIn ? (uint256(r0), uint256(r1)) : (uint256(r1), uint256(r0));
172 }
173 
174 function swapExactTokensForTokens(
175 uint256 amountIn,
176 uint256 amountOutMin,
177 address[] calldata path,
178 address to,
179 uint256
180 ) external returns (uint256[] memory amounts) {
181 amounts = new uint256[](path.length);
182 amounts[0] = amountIn;
183 
184 for (uint256 i; i + 1 < path.length; ++i) {
185 address tokenIn = path[i];
186 address tokenOut = path[i + 1];
187 MockUniV2Pair pair = MockUniV2Pair(pairFor[tokenIn][tokenOut]);
188 
189 if (i == 0) {
190 IMockERC20(tokenIn).transferFrom(msg.sender, address(pair), amountIn);
191 }
192 // Measure what actually landed (the 401K fee-on-transfer path can skim).
193 (uint256 rIn, uint256 rOut) = _reserves(tokenIn, tokenOut);
194 uint256 actualIn = IMockERC20(tokenIn).balanceOf(address(pair)) - rIn;
195 uint256 out = getAmountOut(actualIn, rIn, rOut);
196 amounts[i + 1] = out;
197 
198 address recipient = i + 2 < path.length ? pairFor[tokenOut][path[i + 2]] : to;
199 (uint256 a0, uint256 a1) = pair.token0() == tokenIn ? (uint256(0), out) : (out, uint256(0));
200 pair.swap(a0, a1, recipient, "");
201 }
202 require(amounts[path.length - 1] >= amountOutMin, "MockRouter: INSUFFICIENT_OUTPUT_AMOUNT");
203 }
204}
205 

Click any line number to deep-link to it — the target line highlights on load.