Skip to content

Commit cc86d0f

Browse files
committed
Add Uniswap V2 test
1 parent 68b283d commit cc86d0f

File tree

16 files changed

+788
-158
lines changed

16 files changed

+788
-158
lines changed

docs/targets/stylus.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,46 @@ But in Stylus, one would have to write the program something like this:
7979
}
8080
}
8181
82+
WASM files are size limited
83+
___________________________
84+
85+
In Arbitrum Stylus, the size of a Brotli-compressed WASM file must `not exceed 24KB <https://docs.arbitrum.io/stylus/how-tos/optimizing-binaries>`_.
86+
This can be inhibiting.
87+
For example, one cannot simply compile and deploy the ``UniswapV2Factory`` contract because it would exceed this size limit.
88+
89+
The bulk of the ``UniswapV2Factory``'s size comes from the ``UniswapV2Pair`` contract.
90+
So one way to reduce the ``UniswapV2Factory``'s size is to move the logic to create ``UniswapV2Pair``s into another contract.
91+
The following code snippet shows one way this can be done.
92+
Note that the new ``createPair`` function uses modern Solidity syntax, rather than assembly, to pass a salt to ``new``.
93+
94+
.. code-block:: solidity
95+
96+
interface IUniswapV2PairCreator {
97+
...
98+
function createPair(bytes32 salt) external returns (address pair);
99+
}
100+
101+
import './interfaces/IUniswapV2PairCreator.sol';
102+
import './UniswapV2Pair.sol';
103+
104+
contract UniswapV2PairCreator is IUniswapV2PairCreator {
105+
...
106+
function createPair(bytes32 salt) external returns (address pair) {
107+
UniswapV2Pair pair = new UniswapV2Pair{salt: salt}();
108+
pair.setFactoryAndBase(msg.sender, base);
109+
return address(pair);
110+
}
111+
}
112+
113+
...
114+
// In `UniswapV2Factory`, remove all references to the `UniswapV2Pair` contract and change the creation code as follows.
115+
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
116+
// assembly {
117+
// pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
118+
// }
119+
pair = UniswapV2PairCreator(pairCreator).createPair(salt);
120+
...
121+
82122
``block.number``
83123
________________
84124

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
*.abi
2+
*.br
23
*.wasm
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
pragma solidity =0.5.16;
2+
3+
import "./interfaces/IERC20.sol";
4+
import "./interfaces/IUniswapV2Callee.sol";
5+
6+
contract Callee is IUniswapV2Callee {
7+
address token;
8+
9+
function initialize(address _token) external {
10+
token = _token;
11+
}
12+
13+
function uniswapV2Call(
14+
address,
15+
uint,
16+
uint,
17+
bytes calldata
18+
) external {
19+
uint256 balance = IERC20(token).balanceOf(address(this));
20+
IERC20(token).transfer(msg.sender, balance);
21+
}
22+
}

integration/stylus/Uniswap/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ https://github.com/Uniswap/v2-core/tree/master/contracts
33

44
Specifically:
55

6-
- the assembly code was removed
7-
- dead code that resulted from that was removed or commented out
8-
- some small whitespace changes were made
6+
1. Contracts `UniswapV2PairBase` and `UniswapV2PairCreator` were added. The purposes of these contracts is to get `UniswapV2Pair`'s WASM file to compress to under 24KB.
7+
2. The assembly code was removed.
8+
3. Dead code that resulted from 2 was removed or commented out.
99

10-
Sam Moelius (2025-12-16)
10+
Sam Moelius (2025-12-24)

integration/stylus/Uniswap/UniswapV2ERC20.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ contract UniswapV2ERC20 is IUniswapV2ERC20 {
99
string public constant name = 'Uniswap V2';
1010
string public constant symbol = 'UNI-V2';
1111
uint8 public constant decimals = 18;
12-
uint public totalSupply;
12+
uint public totalSupply;
1313
mapping(address => uint) public balanceOf;
1414
mapping(address => mapping(address => uint)) public allowance;
1515

integration/stylus/Uniswap/UniswapV2Factory.sol

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,57 @@
11
pragma solidity =0.5.16;
22

33
import './interfaces/IUniswapV2Factory.sol';
4+
import './interfaces/IUniswapV2PairCreator.sol';
45
import './UniswapV2Pair.sol';
56

67
contract UniswapV2Factory is IUniswapV2Factory {
78
address public feeTo;
89
address public feeToSetter;
10+
address public pairBase;
11+
address public pairCreator;
912

1013
mapping(address => mapping(address => address)) public getPair;
1114
address[] public allPairs;
1215

1316
event PairCreated(address indexed token0, address indexed token1, address pair, uint);
1417

1518
constructor(address _feeToSetter) public {
19+
// feeToSetter = _feeToSetter;
20+
}
21+
22+
function initialize(
23+
address _feeToSetter,
24+
address _pairBase,
25+
address _pairCreator
26+
) public {
27+
require(feeToSetter == address(0), "already initialized");
1628
feeToSetter = _feeToSetter;
29+
pairBase = _pairBase;
30+
pairCreator = _pairCreator;
1731
}
1832

1933
function allPairsLength() external view returns (uint) {
2034
return allPairs.length;
2135
}
2236

2337
function createPair(address tokenA, address tokenB) external returns (address pair) {
24-
require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
25-
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
26-
require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
27-
require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient
38+
require(getPair[tokenA][tokenB] == address(0), 'UniswapV2: PAIR_EXISTS');
39+
require(getPair[tokenB][tokenA] == address(0), 'UniswapV2: PAIR_EXISTS');
2840
// bytes memory bytecode = type(UniswapV2Pair).creationCode;
29-
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
30-
pair = address(new UniswapV2Pair{salt: salt}());
31-
IUniswapV2Pair(pair).initialize(token0, token1);
32-
getPair[token0][token1] = pair;
33-
getPair[token1][token0] = pair; // populate mapping in the reverse direction
41+
// bytes32 salt = keccak256(abi.encodePacked(token0, token1));
42+
// assembly {
43+
// pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
44+
// }
45+
pair = IUniswapV2PairCreator(pairCreator).createPairWithBase(
46+
pairBase,
47+
tokenA,
48+
tokenB,
49+
0
50+
);
51+
getPair[tokenA][tokenB] = pair;
52+
getPair[tokenB][tokenA] = pair; // populate mapping in the reverse direction
3453
allPairs.push(pair);
35-
emit PairCreated(token0, token1, pair, allPairs.length);
54+
emit PairCreated(tokenA, tokenB, pair, allPairs.length);
3655
}
3756

3857
function setFeeTo(address _feeTo) external {

integration/stylus/Uniswap/UniswapV2Pair.sol

Lines changed: 66 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {
1616
bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));
1717

1818
address public factory;
19+
address public base;
1920
address public token0;
2021
address public token1;
2122

@@ -46,156 +47,88 @@ contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {
4647
require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED');
4748
}
4849

49-
event Mint(address indexed sender, uint amount0, uint amount1);
50-
event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
51-
event Swap(
52-
address indexed sender,
53-
uint amount0In,
54-
uint amount1In,
55-
uint amount0Out,
56-
uint amount1Out,
57-
address indexed to
58-
);
59-
event Sync(uint112 reserve0, uint112 reserve1);
50+
// event Mint(address indexed sender, uint amount0, uint amount1);
51+
// event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
52+
// event Swap(
53+
// address indexed sender,
54+
// uint amount0In,
55+
// uint amount1In,
56+
// uint amount0Out,
57+
// uint amount1Out,
58+
// address indexed to
59+
// );
60+
// event Sync(uint112 reserve0, uint112 reserve1);
6061

6162
constructor() public {
62-
factory = msg.sender;
63+
// factory = msg.sender;
64+
}
65+
66+
function setFactoryAndBase(address _factory, address _base) external {
67+
require(factory == address(0), "factory already set");
68+
require(base == address(0), "base already set");
69+
factory = _factory;
70+
base = _base;
71+
bytes memory args = abi.encodeWithSignature(
72+
"setFactoryAndBase(address,address)",
73+
_factory,
74+
_base
75+
);
76+
(, bytes memory result) = base.delegatecall(args);
77+
return abi.decode(result, ());
6378
}
6479

6580
// called once by the factory at time of deployment
6681
function initialize(address _token0, address _token1) external {
67-
require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check
82+
// require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check
6883
token0 = _token0;
6984
token1 = _token1;
85+
bytes memory args = abi.encodeWithSignature(
86+
"initialize(address,address)",
87+
_token0,
88+
_token1
89+
);
90+
(, bytes memory result) = base.delegatecall(args);
91+
return abi.decode(result, ());
7092
}
7193

72-
// update reserves and, on the first call per block, price accumulators
73-
function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {
74-
require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');
75-
uint32 blockTimestamp = uint32(block.timestamp % 2**32);
76-
uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired
77-
if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
78-
// * never overflows, and + overflow is desired
79-
price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
80-
price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
81-
}
82-
reserve0 = uint112(balance0);
83-
reserve1 = uint112(balance1);
84-
blockTimestampLast = blockTimestamp;
85-
emit Sync(reserve0, reserve1);
86-
}
87-
88-
// if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k)
89-
function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {
90-
address feeTo = IUniswapV2Factory(factory).feeTo();
91-
feeOn = feeTo != address(0);
92-
uint _kLast = kLast; // gas savings
93-
if (feeOn) {
94-
if (_kLast != 0) {
95-
uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));
96-
uint rootKLast = Math.sqrt(_kLast);
97-
if (rootK > rootKLast) {
98-
uint numerator = totalSupply.mul(rootK.sub(rootKLast));
99-
uint denominator = rootK.mul(5).add(rootKLast);
100-
uint liquidity = numerator / denominator;
101-
if (liquidity > 0) _mint(feeTo, liquidity);
102-
}
103-
}
104-
} else if (_kLast != 0) {
105-
kLast = 0;
106-
}
107-
}
108-
109-
// this low-level function should be called from a contract which performs important safety checks
110-
function mint(address to) external lock returns (uint liquidity) {
111-
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
112-
uint balance0 = IERC20(token0).balanceOf(address(this));
113-
uint balance1 = IERC20(token1).balanceOf(address(this));
114-
uint amount0 = balance0.sub(_reserve0);
115-
uint amount1 = balance1.sub(_reserve1);
116-
117-
bool feeOn = _mintFee(_reserve0, _reserve1);
118-
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
119-
if (_totalSupply == 0) {
120-
liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
121-
_mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
122-
} else {
123-
liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
124-
}
125-
require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
126-
_mint(to, liquidity);
127-
128-
_update(balance0, balance1, _reserve0, _reserve1);
129-
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
130-
emit Mint(msg.sender, amount0, amount1);
94+
function mint(address to) external returns (uint liquidity) {
95+
bytes memory args = abi.encodeWithSignature("mint(address)", to);
96+
(, bytes memory result) = base.delegatecall(args);
97+
return abi.decode(result, (uint));
13198
}
13299

133-
// this low-level function should be called from a contract which performs important safety checks
134-
function burn(address to) external lock returns (uint amount0, uint amount1) {
135-
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
136-
address _token0 = token0; // gas savings
137-
address _token1 = token1; // gas savings
138-
uint balance0 = IERC20(_token0).balanceOf(address(this));
139-
uint balance1 = IERC20(_token1).balanceOf(address(this));
140-
uint liquidity = balanceOf[address(this)];
141-
142-
bool feeOn = _mintFee(_reserve0, _reserve1);
143-
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
144-
amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution
145-
amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution
146-
require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');
147-
_burn(address(this), liquidity);
148-
_safeTransfer(_token0, to, amount0);
149-
_safeTransfer(_token1, to, amount1);
150-
balance0 = IERC20(_token0).balanceOf(address(this));
151-
balance1 = IERC20(_token1).balanceOf(address(this));
152-
153-
_update(balance0, balance1, _reserve0, _reserve1);
154-
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
155-
emit Burn(msg.sender, amount0, amount1, to);
100+
function burn(address to) external returns (uint amount0, uint amount1) {
101+
bytes memory args = abi.encodeWithSignature("burn(address)", to);
102+
(, bytes memory result) = base.delegatecall(args);
103+
return abi.decode(result, (uint, uint));
156104
}
157105

158-
// this low-level function should be called from a contract which performs important safety checks
159-
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
160-
require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
161-
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
162-
require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
163-
164-
uint balance0;
165-
uint balance1;
166-
{ // scope for _token{0,1}, avoids stack too deep errors
167-
address _token0 = token0;
168-
address _token1 = token1;
169-
require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
170-
if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
171-
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
172-
if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
173-
balance0 = IERC20(_token0).balanceOf(address(this));
174-
balance1 = IERC20(_token1).balanceOf(address(this));
175-
}
176-
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
177-
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
178-
require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
179-
{ // scope for reserve{0,1}Adjusted, avoids stack too deep errors
180-
uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
181-
uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
182-
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
183-
}
184-
185-
_update(balance0, balance1, _reserve0, _reserve1);
186-
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
106+
function swap(
107+
uint amount0Out,
108+
uint amount1Out,
109+
address to,
110+
bytes calldata data
111+
) external {
112+
bytes memory args = abi.encodeWithSignature(
113+
"swap(uint256,uint256,address,bytes)",
114+
amount0Out,
115+
amount1Out,
116+
to,
117+
data
118+
);
119+
(, bytes memory result) = base.delegatecall(args);
120+
return abi.decode(result, ());
187121
}
188122

189-
// force balances to match reserves
190-
function skim(address to) external lock {
191-
address _token0 = token0; // gas savings
192-
address _token1 = token1; // gas savings
193-
_safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));
194-
_safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));
123+
function skim(address to) external {
124+
bytes memory args = abi.encodeWithSignature("skim(address)", to);
125+
(, bytes memory result) = base.delegatecall(args);
126+
return abi.decode(result, ());
195127
}
196128

197-
// force reserves to match balances
198-
function sync() external lock {
199-
_update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
129+
function sync() external {
130+
bytes memory args = abi.encodeWithSignature("sync()");
131+
(, bytes memory result) = base.delegatecall(args);
132+
return abi.decode(result, ());
200133
}
201134
}

0 commit comments

Comments
 (0)