Routing in Assembly
Solidity
Uniswap
Yul
Routing V3
Original Contract: SwapRouter.sol
uniswapV3SwapCallback
SwapRouter.sol
1// SPDX-License-Identifier: GPL-2.0-or-later
2pragma solidity =0.7.6;
3pragma abicoder v2;
4
5contract SwapRouter {
6 function uniswapV3SwapCallback(
7 int256 amount0Delta,
8 int256 amount1Delta,
9 bytes calldata _data
10 ) external override {
11 require(amount0Delta > 0 || amount1Delta > 0); // swaps entirely within 0-liquidity regions are not supported
12 SwapCallbackData memory data = abi.decode(_data, (SwapCallbackData));
13 (address tokenIn, address tokenOut, uint24 fee) = data.path.decodeFirstPool();
14 CallbackValidation.verifyCallback(factory, tokenIn, tokenOut, fee);
15
16 (bool isExactInput, uint256 amountToPay) =
17 amount0Delta > 0
18 ? (tokenIn < tokenOut, uint256(amount0Delta))
19 : (tokenOut < tokenIn, uint256(amount1Delta));
20 if (isExactInput) {
21 pay(tokenIn, data.payer, msg.sender, amountToPay);
22 } else {
23 // either initiate the next swap or pay
24 if (data.path.hasMultiplePools()) {
25 data.path = data.path.skipToken();
26 exactOutputInternal(amountToPay, msg.sender, 0, data);
27 } else {
28 amountInCached = amountToPay;
29 tokenIn = tokenOut; // swap in/out because exact output swaps are reversed
30 pay(tokenIn, data.payer, msg.sender, amountToPay);
31 }
32 }
33 }
34}
V3Routing.sol
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.25;
3
4contract V3Routing {
5 function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external {
6 assembly ("memory-safe") {
7 function require(condition, selector) {
8 if iszero(condition) {
9 mstore(0x00, selector)
10 revert(0x1c, 0x04)
11 }
12 }
13
14 function ternary(condition, x, y) -> z {
15 z := xor(y, mul(xor(x, y), iszero(iszero(condition))))
16 }
17
18 function decodePool(offset) -> tokenIn, tokenOut, fee {
19 let firstWord := calldataload(offset)
20 tokenIn := shr(0x60, firstWord)
21 fee := and(shr(0x48, firstWord), 0xffffff)
22 tokenOut := shr(0x60, calldataload(add(offset, NEXT_OFFSET)))
23 }
24
25 function getPool(ptr, token0, token1, fee) -> pool {
26 // sort tokens if necessary
27 if gt(token0, token1) {
28 let temp := token0
29 token0 := token1
30 token1 := temp
31 }
32
33 // store the addresses of token0, token1, and fee to compute the salt of the pool
34 mstore(add(ptr, 0x15), token0)
35 mstore(add(ptr, 0x35), token1)
36 mstore(add(ptr, 0x55), fee)
37
38 // store the address of the factory, computed salt, and the init code hash of the pool
39 mstore(ptr, add(hex"ff", shl(0x58, UNISWAP_V3_FACTORY)))
40 mstore(add(ptr, 0x15), keccak256(add(ptr, 0x15), 0x60))
41 mstore(add(ptr, 0x35), UNISWAP_V3_POOL_INIT_CODE_HASH)
42
43 pool := and(keccak256(ptr, 0x55), 0xffffffffffffffffffffffffffffffffffffffff)
44
45 // revert if the pool at computed address is not deployed yet
46 require(iszero(iszero(extcodesize(pool))), 0x0ba98f1c) // PoolNotExists()
47 }
48
49 function pay(ptr, token, payer, amount) {
50 switch and(eq(token, WETH), iszero(lt(selfbalance(), amount)))
51 case 0x01 {
52 // wrap ETH into WETH, then pay the pool
53 mstore(ptr, 0xd0e30db000000000000000000000000000000000000000000000000000000000) // deposit()
54
55 if iszero(call(gas(), WETH, amount, ptr, 0x04, 0x00, 0x00)) {
56 returndatacopy(ptr, 0x00, returndatasize())
57 revert(ptr, returndatasize())
58 }
59
60 mstore(ptr, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) // transfer(address,uint256)
61 mstore(add(ptr, 0x04), caller())
62 mstore(add(ptr, 0x24), amount)
63
64 if iszero(
65 and(
66 or(eq(mload(0x00), 0x01), iszero(returndatasize())),
67 call(gas(), WETH, 0x00, ptr, 0x44, 0x00, 0x20)
68 )
69 ) {
70 mstore(0x00, 0x90b8ec18) // TransferFailed()
71 revert(0x1c, 0x04)
72 }
73 }
74 default {
75 switch eq(payer, address())
76 case 0x00 {
77 mstore(ptr, 0x23b872dd00000000000000000000000000000000000000000000000000000000) // transferFrom(address,address,uint256)
78 mstore(add(ptr, 0x04), payer)
79 mstore(add(ptr, 0x24), caller())
80 mstore(add(ptr, 0x44), amount)
81
82 if iszero(
83 and(
84 or(eq(mload(0x00), 0x01), iszero(returndatasize())),
85 call(gas(), token, 0x00, ptr, 0x64, 0x00, 0x20)
86 )
87 ) {
88 mstore(0x00, 0x7939f424) // TransferFromFailed()
89 revert(0x1c, 0x04)
90 }
91 }
92 default {
93 mstore(ptr, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) // transfer(address,uint256)
94 mstore(add(ptr, 0x04), caller())
95 mstore(add(ptr, 0x24), amount)
96
97 if iszero(
98 and(
99 or(eq(mload(0x00), 0x01), iszero(returndatasize())),
100 call(gas(), token, 0x00, ptr, 0x44, 0x00, 0x20)
101 )
102 ) {
103 mstore(0x00, 0x90b8ec18) // TransferFailed()
104 revert(0x1c, 0x04)
105 }
106 }
107 }
108 }
109
110 require(or(sgt(amount0Delta, 0x00), sgt(amount1Delta, 0x00)), 0x11157667) // InvalidSwap()
111
112 // decode the address of the payer from the data
113 let payerOffset := calldataload(add(data.offset, calldataload(data.offset)))
114 let payer := and(calldataload(add(data.offset, payerOffset)), 0xffffffffffffffffffffffffffffffffffffffff)
115 require(iszero(iszero(payer)), 0x8eb5b891) // InvalidPayer()
116
117 // extract the path for the swap from the data
118 let pathLength := calldataload(add(data.offset, add(payerOffset, 0x20)))
119 let pathOffset := add(data.offset, add(payerOffset, 0x40))
120
121 // decode the salt of the first pool from the path
122 let tokenIn, tokenOut, fee := decodePool(pathOffset)
123
124 // determine the direction of the current swap and amount of tokens to be paid to the pool
125 let isExactInput
126 let amountToPay
127
128 switch sgt(amount0Delta, 0x00)
129 case 0x00 {
130 isExactInput := lt(tokenOut, tokenIn)
131 amountToPay := amount1Delta
132 }
133 default {
134 isExactInput := lt(tokenIn, tokenOut)
135 amountToPay := amount0Delta
136 }
137
138 // get a free memory pointer
139 let ptr := mload(0x40)
140
141 // compute the address of the first pool
142 let pool := getPool(ptr, tokenIn, tokenOut, fee)
143 require(eq(pool, caller()), 0x2083cd40) // InvalidPool()
144
145 switch iszero(isExactInput)
146 case 0x00 {
147 // pay the pool
148 pay(ptr, tokenIn, payer, amountToPay)
149 }
150 default {
151 switch lt(pathLength, MULTIPLE_POOLS_MIN_LENGTH)
152 case 0x00 {
153 // update the path by slicing out the first token and fee for the next iteration
154 pathOffset := add(pathOffset, NEXT_OFFSET)
155 pathLength := sub(pathLength, NEXT_OFFSET)
156
157 // determine the full length of the path, padded with zeros to the right
158 let pathSize := add(pathLength, sub(0x20, mod(pathLength, 0x20)))
159
160 // decode the salt of the next pool from the path
161 tokenOut, tokenIn, fee := decodePool(pathOffset)
162 let zeroForOne := lt(tokenIn, tokenOut)
163
164 // compute the address of the next pool
165 pool := getPool(ptr, tokenIn, tokenOut, fee)
166
167 // encode the calldata with swap parameters, then perform the swap
168 mstore(ptr, 0x128acb0800000000000000000000000000000000000000000000000000000000) // swap(address,bool,int256,uint160,bytes)
169 mstore(add(ptr, 0x04), caller())
170 mstore(add(ptr, 0x24), zeroForOne)
171 mstore(add(ptr, 0x44), not(sub(amountToPay, 0x01)))
172 mstore(add(ptr, 0x64), ternary(zeroForOne, MIN_SQRT_PRICE_LIMIT, MAX_SQRT_PRICE_LIMIT))
173 mstore(add(ptr, 0x84), 0xa0)
174 mstore(add(ptr, 0xa4), shl(0x05, add(div(pathSize, 0x20), 0x04)))
175 mstore(add(ptr, 0xc4), 0x20)
176 mstore(add(ptr, 0xe4), 0x40)
177 mstore(add(ptr, 0x104), payer)
178 mstore(add(ptr, 0x124), pathLength)
179 calldatacopy(add(ptr, 0x144), pathOffset, pathSize)
180
181 if iszero(call(gas(), pool, 0x00, ptr, add(0x144, pathSize), 0x00, 0x40)) {
182 returndatacopy(ptr, 0x00, returndatasize())
183 revert(ptr, returndatasize())
184 }
185 }
186 default {
187 // pay the pool; because exact output swaps are executed in reverse order, tokenOut is actually tokenIn
188 pay(ptr, tokenOut, payer, amountToPay)
189
190 // cache amount of tokens paid to the pool
191 sstore(AMOUNT_IN_CACHED_SLOT, amountToPay)
192 }
193 }
194 }
195 }
196}
exactInput
SwapRouter.sol
1// SPDX-License-Identifier: GPL-2.0-or-later
2pragma solidity =0.7.6;
3pragma abicoder v2;
4
5contract SwapRouter {
6 struct ExactInputParams {
7 bytes path;
8 address recipient;
9 uint256 deadline;
10 uint256 amountIn;
11 uint256 amountOutMinimum;
12 }
13
14 function exactInput(ExactInputParams memory params)
15 external
16 payable
17 override
18 checkDeadline(params.deadline)
19 returns (uint256 amountOut)
20 {
21 address payer = msg.sender; // msg.sender pays for the first hop
22
23 while (true) {
24 bool hasMultiplePools = params.path.hasMultiplePools();
25
26 // the outputs of prior swaps become the inputs to subsequent ones
27 params.amountIn = exactInputInternal(
28 params.amountIn,
29 hasMultiplePools ? address(this) : params.recipient, // for intermediate swaps, this contract custodies
30 0,
31 SwapCallbackData({
32 path: params.path.getFirstPool(), // only the first pool in the path is necessary
33 payer: payer
34 })
35 );
36
37 // decide whether to continue or terminate
38 if (hasMultiplePools) {
39 payer = address(this); // at this point, the caller has paid
40 params.path = params.path.skipToken();
41 } else {
42 amountOut = params.amountIn;
43 break;
44 }
45 }
46
47 require(amountOut >= params.amountOutMinimum, 'Too little received');
48 }
49
50 function exactInputInternal(
51 uint256 amountIn,
52 address recipient,
53 uint160 sqrtPriceLimitX96,
54 SwapCallbackData memory data
55 ) private returns (uint256 amountOut) {
56 // allow swapping to the router address with address 0
57 if (recipient == address(0)) recipient = address(this);
58
59 (address tokenIn, address tokenOut, uint24 fee) = data.path.decodeFirstPool();
60
61 bool zeroForOne = tokenIn < tokenOut;
62
63 (int256 amount0, int256 amount1) =
64 getPool(tokenIn, tokenOut, fee).swap(
65 recipient,
66 zeroForOne,
67 amountIn.toInt256(),
68 sqrtPriceLimitX96 == 0
69 ? (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1)
70 : sqrtPriceLimitX96,
71 abi.encode(data)
72 );
73
74 return uint256(-(zeroForOne ? amount1 : amount0));
75 }
76}
V3Routing.sol
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.25;
3
4contract V3Routing {
5 function exactInput(
6 bytes calldata path,
7 address recipient,
8 uint256 amountIn,
9 uint256 amountOutMin,
10 uint256 deadline
11 ) external payable returns (uint256 amountOut) {
12 assembly ("memory-safe") {
13 function require(condition, selector) {
14 if iszero(condition) {
15 mstore(0x00, selector)
16 revert(0x1c, 0x04)
17 }
18 }
19
20 function ternary(condition, x, y) -> z {
21 z := xor(y, mul(xor(x, y), iszero(iszero(condition))))
22 }
23
24 function decodePool(offset) -> tokenIn, tokenOut, fee {
25 let firstWord := calldataload(offset)
26 tokenIn := shr(0x60, firstWord)
27 fee := and(shr(0x48, firstWord), 0xffffff)
28 tokenOut := shr(0x60, calldataload(add(offset, NEXT_OFFSET)))
29 }
30
31 function getPool(ptr, token0, token1, fee) -> pool {
32 // sort tokens if necessary
33 if gt(token0, token1) {
34 let temp := token0
35 token0 := token1
36 token1 := temp
37 }
38
39 // store the addresses of token0, token1, and fee to compute the salt of the pool
40 mstore(add(ptr, 0x15), token0)
41 mstore(add(ptr, 0x35), token1)
42 mstore(add(ptr, 0x55), fee)
43
44 // store the address of the factory, computed salt, and the init code hash of the pool
45 mstore(ptr, add(hex"ff", shl(0x58, UNISWAP_V3_FACTORY)))
46 mstore(add(ptr, 0x15), keccak256(add(ptr, 0x15), 0x60))
47 mstore(add(ptr, 0x35), UNISWAP_V3_POOL_INIT_CODE_HASH)
48
49 pool := and(keccak256(ptr, 0x55), 0xffffffffffffffffffffffffffffffffffffffff)
50
51 // revert if the pool at computed address is not deployed yet
52 require(iszero(iszero(extcodesize(pool))), 0x0ba98f1c) // PoolNotExists()
53 }
54
55 require(iszero(lt(deadline, timestamp())), 0x1ab7da6b) // DeadlineExpired()
56 require(iszero(mod(sub(path.length, ADDR_SIZE), NEXT_OFFSET)), 0x20db8267) // InvalidPath()
57 require(iszero(iszero(amountIn)), 0xdf5b2ee6) // InsufficientAmountIn()
58
59 // the caller pays for the first hop
60 let payer := caller()
61
62 // get a free memory pointer, then allocate memory
63 let ptr := mload(0x40)
64 mstore(0x40, add(ptr, 0x184))
65
66 // prettier-ignore
67 for { } 0x01 { } {
68 // determine whether the path includes multiple pools
69 let hasMultiplePools := iszero(lt(path.length, MULTIPLE_POOLS_MIN_LENGTH))
70
71 // decode the salt of the current pool from the path
72 let tokenIn, tokenOut, fee := decodePool(path.offset)
73 let zeroForOne := lt(tokenIn, tokenOut)
74
75 // compute the address of the current pool
76 let pool := getPool(ptr, tokenIn, tokenOut, fee)
77
78 // encode the calldata with swap parameters, then perform the swap;
79 // some parameters can be set with constant values, as exact input swaps only require the first pool in the path
80 mstore(ptr, 0x128acb0800000000000000000000000000000000000000000000000000000000) // swap(address,bool,int256,uint160,bytes)
81 mstore(add(ptr, 0x04), ternary(hasMultiplePools, address(), recipient))
82 mstore(add(ptr, 0x24), zeroForOne)
83 mstore(add(ptr, 0x44), amountIn)
84 mstore(add(ptr, 0x64), ternary(zeroForOne, MIN_SQRT_PRICE_LIMIT, MAX_SQRT_PRICE_LIMIT))
85 mstore(add(ptr, 0x84), 0xa0)
86 mstore(add(ptr, 0xa4), 0xc0)
87 mstore(add(ptr, 0xc4), 0x20)
88 mstore(add(ptr, 0xe4), 0x40)
89 mstore(add(ptr, 0x104), payer)
90 mstore(add(ptr, 0x124), POP_OFFSET) // only the first pool is required
91 calldatacopy(add(ptr, 0x144), path.offset, 0x40)
92
93 if iszero(call(gas(), pool, 0x00, ptr, 0x184, 0x00, 0x40)) {
94 returndatacopy(ptr, 0x00, returndatasize())
95 revert(ptr, returndatasize())
96 }
97
98 amountIn := add(not(ternary(zeroForOne, mload(0x20), mload(0x00))), 0x01)
99
100 switch iszero(hasMultiplePools)
101 case 0x00 {
102 // update the address of the payer if it is not assigned to the address of this contract;
103 // the caller has made the payment at this point
104 if xor(payer, address()) {
105 payer := address()
106 }
107
108 // update the path by slicing out the first token and fee for the next iteration
109 path.offset := add(path.offset, NEXT_OFFSET)
110 path.length := sub(path.length, NEXT_OFFSET)
111 }
112 default {
113 amountOut := amountIn
114 break
115 }
116 }
117
118 require(iszero(lt(amountOut, amountOutMin)), 0xe52970aa) // InsufficientAmountOut()
119 }
120 }
121}
exactOutput
SwapRouter.sol
1// SPDX-License-Identifier: GPL-2.0-or-later
2pragma solidity =0.7.6;
3pragma abicoder v2;
4
5contract SwapRouter {
6 struct ExactOutputParams {
7 bytes path;
8 address recipient;
9 uint256 deadline;
10 uint256 amountOut;
11 uint256 amountInMaximum;
12 }
13
14 function exactOutput(ExactOutputParams calldata params)
15 external
16 payable
17 override
18 checkDeadline(params.deadline)
19 returns (uint256 amountIn)
20 {
21 // it's okay that the payer is fixed to msg.sender here, as they're only paying for the "final" exact output
22 // swap, which happens first, and subsequent swaps are paid for within nested callback frames
23 exactOutputInternal(
24 params.amountOut,
25 params.recipient,
26 0,
27 SwapCallbackData({path: params.path, payer: msg.sender})
28 );
29
30 amountIn = amountInCached;
31 require(amountIn <= params.amountInMaximum, 'Too much requested');
32 amountInCached = DEFAULT_AMOUNT_IN_CACHED;
33 }
34
35 function exactOutputInternal(
36 uint256 amountOut,
37 address recipient,
38 uint160 sqrtPriceLimitX96,
39 SwapCallbackData memory data
40 ) private returns (uint256 amountIn) {
41 // allow swapping to the router address with address 0
42 if (recipient == address(0)) recipient = address(this);
43
44 (address tokenOut, address tokenIn, uint24 fee) = data.path.decodeFirstPool();
45
46 bool zeroForOne = tokenIn < tokenOut;
47
48 (int256 amount0Delta, int256 amount1Delta) =
49 getPool(tokenIn, tokenOut, fee).swap(
50 recipient,
51 zeroForOne,
52 -amountOut.toInt256(),
53 sqrtPriceLimitX96 == 0
54 ? (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1)
55 : sqrtPriceLimitX96,
56 abi.encode(data)
57 );
58
59 uint256 amountOutReceived;
60 (amountIn, amountOutReceived) = zeroForOne
61 ? (uint256(amount0Delta), uint256(-amount1Delta))
62 : (uint256(amount1Delta), uint256(-amount0Delta));
63 // it's technically possible to not receive the full output amount,
64 // so if no price limit has been specified, require this possibility away
65 if (sqrtPriceLimitX96 == 0) require(amountOutReceived == amountOut);
66 }
67}
V3Routing.sol
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.25;
3
4contract V3Routing {
5 function exactOutput(
6 bytes calldata path,
7 address recipient,
8 uint256 amountOut,
9 uint256 amountInMax,
10 uint256 deadline
11 ) external payable returns (uint256 amountIn) {
12 assembly ("memory-safe") {
13 function require(condition, selector) {
14 if iszero(condition) {
15 mstore(0x00, selector)
16 revert(0x1c, 0x04)
17 }
18 }
19
20 function ternary(condition, x, y) -> z {
21 z := xor(y, mul(xor(x, y), iszero(iszero(condition))))
22 }
23
24 function decodePool(offset) -> tokenOut, tokenIn, fee {
25 let firstWord := calldataload(offset)
26 tokenOut := shr(0x60, firstWord)
27 fee := and(shr(0x48, firstWord), 0xffffff)
28 tokenIn := shr(0x60, calldataload(add(offset, NEXT_OFFSET)))
29 }
30
31 function getPool(ptr, token0, token1, fee) -> pool {
32 // sort tokens if necessary
33 if gt(token0, token1) {
34 let temp := token0
35 token0 := token1
36 token1 := temp
37 }
38
39 // store the addresses of token0, token1, and fee to compute the salt of the pool
40 mstore(add(ptr, 0x15), token0)
41 mstore(add(ptr, 0x35), token1)
42 mstore(add(ptr, 0x55), fee)
43
44 // store the address of the factory, computed salt, and the init code hash of the pool
45 mstore(ptr, add(hex"ff", shl(0x58, UNISWAP_V3_FACTORY)))
46 mstore(add(ptr, 0x15), keccak256(add(ptr, 0x15), 0x60))
47 mstore(add(ptr, 0x35), UNISWAP_V3_POOL_INIT_CODE_HASH)
48
49 pool := and(keccak256(ptr, 0x55), 0xffffffffffffffffffffffffffffffffffffffff)
50
51 // revert if the pool at computed address is not deployed yet
52 require(iszero(iszero(extcodesize(pool))), 0x0ba98f1c) // PoolNotExists()
53 }
54
55 require(iszero(lt(deadline, timestamp())), 0x1ab7da6b) // DeadlineExpired()
56 require(iszero(mod(sub(path.length, ADDR_SIZE), NEXT_OFFSET)), 0x20db8267) // InvalidPath()
57 require(iszero(iszero(amountInMax)), 0xdf5b2ee6) // InsufficientAmountIn()
58
59 // determine the full length of the path, padded with zeros to the right
60 let pathSize := add(path.length, sub(0x20, mod(path.length, 0x20)))
61
62 // get a free memory pointer, then allocate memory
63 let ptr := mload(0x40)
64 mstore(0x40, add(ptr, add(0x144, pathSize)))
65
66 // decode the salt of the first pool from the path
67 let tokenOut, tokenIn, fee := decodePool(path.offset)
68 let zeroForOne := lt(tokenIn, tokenOut)
69
70 // compute the address of the pool
71 let pool := getPool(ptr, tokenIn, tokenOut, fee)
72
73 // encode the calldata with swap parameters, then execute the swap
74 mstore(ptr, 0x128acb0800000000000000000000000000000000000000000000000000000000) // swap(address,bool,int256,uint160,bytes)
75 mstore(add(ptr, 0x04), recipient)
76 mstore(add(ptr, 0x24), zeroForOne)
77 mstore(add(ptr, 0x44), not(sub(amountOut, 0x01)))
78 mstore(add(ptr, 0x64), ternary(zeroForOne, MIN_SQRT_PRICE_LIMIT, MAX_SQRT_PRICE_LIMIT))
79 mstore(add(ptr, 0x84), 0xa0)
80 mstore(add(ptr, 0xa4), shl(0x05, add(div(pathSize, 0x20), 0x04)))
81 mstore(add(ptr, 0xc4), 0x20)
82 mstore(add(ptr, 0xe4), 0x40)
83 mstore(add(ptr, 0x104), caller())
84 mstore(add(ptr, 0x124), path.length)
85 calldatacopy(add(ptr, 0x144), path.offset, pathSize)
86
87 if iszero(call(gas(), pool, 0x00, ptr, add(0x144, pathSize), 0x00, 0x40)) {
88 returndatacopy(ptr, 0x00, returndatasize())
89 revert(ptr, returndatasize())
90 }
91
92 // validate that the amount of tokens received is equal to the specified amount out
93 let amountOutReceived := add(not(ternary(zeroForOne, mload(0x20), mload(0x00))), 0x01)
94 require(eq(amountOutReceived, amountOut), 0xe52970aa) // InsufficientAmountOut()
95
96 // retrieve the cached amount in from the slot, then validate that it does not exceed the maximum limit
97 amountIn := sload(AMOUNT_IN_CACHED_SLOT)
98 require(iszero(gt(amountIn, amountInMax)), 0xdf5b2ee6) // InsufficientAmountIn()
99
100 // reset the cached amount in to the default value
101 sstore(AMOUNT_IN_CACHED_SLOT, sub(shl(256, 1), 1))
102 }
103 }
104}
Routing V2
Original Contract: UniswapV2Router01.sol
swapExactTokensForTokens
UniswapV2Router01.sol
1// SPDX-License-Identifier: MIT
2pragma solidity =0.6.6;
3
4contract UniswapV2Router01 {
5 function swapExactTokensForTokens(
6 uint amountIn,
7 uint amountOutMin,
8 address[] calldata path,
9 address to,
10 uint deadline
11 ) external override ensure(deadline) returns (uint[] memory amounts) {
12 amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
13 require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
14 TransferHelper.safeTransferFrom(path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]);
15 _swap(amounts, path, to);
16 }
17
18 function _swap(uint[] memory amounts, address[] memory path, address _to) private {
19 for (uint i; i < path.length - 1; i++) {
20 (address input, address output) = (path[i], path[i + 1]);
21 (address token0,) = UniswapV2Library.sortTokens(input, output);
22 uint amountOut = amounts[i + 1];
23 (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
24 address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
25 IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(amount0Out, amount1Out, to, new bytes(0));
26 }
27 }
28}
V2Routing.sol
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.25;
3
4contract V2Routing {
5 function swapExactTokensForTokens(
6 Currency[] calldata path,
7 address recipient,
8 uint256 amountIn,
9 uint256 amountOutMin,
10 uint256 deadline
11 ) external payable returns (uint256 amountOut) {
12 assembly ("memory-safe") {
13 function require(condition, selector) {
14 if iszero(condition) {
15 mstore(0x00, selector)
16 revert(0x1c, 0x04)
17 }
18 }
19
20 function ternary(condition, x, y) -> z {
21 z := xor(y, mul(xor(x, y), iszero(iszero(condition))))
22 }
23
24 function pairFor(ptr, token0, token1) -> pair {
25 // sort tokens if necessary
26 if gt(token0, token1) {
27 let temp := token0
28 token0 := token1
29 token1 := temp
30 }
31
32 // store the addresses of token0 and token1
33 mstore(ptr, shl(0x60, token0))
34 mstore(add(ptr, 0x14), shl(0x60, token1))
35
36 // compute the salt of the pair
37 let salt := keccak256(ptr, 0x28)
38
39 // store the address of the factory, computed salt, and the init code hash of the pair
40 mstore(ptr, add(hex"ff", shl(0x58, UNISWAP_V2_FACTORY)))
41 mstore(add(ptr, 0x15), salt)
42 mstore(add(ptr, 0x35), UNISWAP_V2_PAIR_INIT_CODE_HASH)
43
44 pair := and(keccak256(ptr, 0x55), 0xffffffffffffffffffffffffffffffffffffffff)
45
46 // revert if the pair at computed address is not deployed yet
47 require(iszero(iszero(extcodesize(pair))), 0x0022d46a) // PairNotExists()
48 }
49
50 function getReserves(ptr, pair, zeroForOne) -> reserveIn, reserveOut {
51 // fetch the reserves of the pair; swap positions if necessary
52 mstore(ptr, 0x0902f1ac00000000000000000000000000000000000000000000000000000000) // getReserves()
53
54 if iszero(staticcall(gas(), pair, ptr, 0x04, ptr, 0x40)) {
55 returndatacopy(ptr, 0x00, returndatasize())
56 revert(ptr, returndatasize())
57 }
58
59 switch zeroForOne
60 case 0x00 {
61 reserveOut := mload(ptr)
62 reserveIn := mload(add(ptr, 0x20))
63 }
64 default {
65 reserveIn := mload(ptr)
66 reserveOut := mload(add(ptr, 0x20))
67 }
68
69 require(and(iszero(iszero(reserveIn)), iszero(iszero(reserveOut))), 0x2f76b1d4) // InsuffcientReserves()
70 }
71
72 function swap(ptr, pair, zeroForOne, value, to) {
73 // encode the calldata with swap parameters, then perform the swap
74 mstore(ptr, 0x022c0d9f00000000000000000000000000000000000000000000000000000000) // swap(uint256,uint256,address,bytes)
75 mstore(add(ptr, 0x04), mul(value, iszero(zeroForOne)))
76 mstore(add(ptr, 0x24), mul(value, iszero(iszero(zeroForOne))))
77 mstore(add(ptr, 0x44), to)
78 mstore(add(ptr, 0x64), 0x80)
79
80 if iszero(call(gas(), pair, 0x00, ptr, 0xa4, 0x00, 0x00)) {
81 returndatacopy(ptr, 0x00, returndatasize())
82 revert(ptr, returndatasize())
83 }
84 }
85
86 require(iszero(lt(deadline, timestamp())), 0x1ab7da6b) // DeadlineExpired()
87 require(gt(path.length, 0x01), 0x20db8267) // InvalidPath()
88 require(iszero(iszero(amountIn)), 0xdf5b2ee6) // InsufficientAmountIn()
89
90 // get a free memory pointer, then allocate memory
91 let ptr := mload(0x40)
92 mstore(0x40, add(ptr, 0xc0))
93
94 let lastIndex := sub(path.length, 0x01)
95 let penultimateIndex := sub(lastIndex, 0x01)
96
97 // retrieve the address of the input token
98 let token := calldataload(path.offset)
99
100 // compute the address of the first pair
101 let pair := pairFor(ptr, token, calldataload(add(path.offset, 0x20)))
102
103 // wrap ETH before the iteration if the caller is paying with ETH; otherwise, pay the first pair with the input token
104 switch and(eq(token, WETH), iszero(lt(selfbalance(), amountIn)))
105 case 0x00 {
106 require(iszero(callvalue()), 0x21a64d90) // InvalidCallValue()
107
108 mstore(ptr, 0x23b872dd00000000000000000000000000000000000000000000000000000000) // transferFrom(address,address,uint256)
109 mstore(add(ptr, 0x04), caller())
110 mstore(add(ptr, 0x24), pair)
111 mstore(add(ptr, 0x44), amountIn)
112
113 if iszero(
114 and(
115 or(eq(mload(0x00), 0x01), iszero(returndatasize())),
116 call(gas(), token, 0x00, ptr, 0x64, 0x00, 0x20)
117 )
118 ) {
119 mstore(0x00, 0x7939f424) // TransferFromFailed()
120 revert(0x1c, 0x04)
121 }
122 }
123 default {
124 mstore(ptr, 0xd0e30db000000000000000000000000000000000000000000000000000000000) // deposit()
125
126 if iszero(call(gas(), WETH, amountIn, ptr, 0x04, 0x00, 0x00)) {
127 returndatacopy(ptr, 0x00, returndatasize())
128 revert(ptr, returndatasize())
129 }
130
131 mstore(ptr, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) // transfer(address,uint256)
132 mstore(add(ptr, 0x04), pair)
133 mstore(add(ptr, 0x24), amountIn)
134
135 if iszero(
136 and(
137 or(eq(mload(0x00), 0x01), iszero(returndatasize())),
138 call(gas(), WETH, 0x00, ptr, 0x44, 0x00, 0x20)
139 )
140 ) {
141 mstore(0x00, 0x90b8ec18) // TransferFailed()
142 revert(0x1c, 0x04)
143 }
144 }
145
146 amountOut := amountIn
147
148 // prettier-ignore
149 // iterate over the path array, performing a swap for each pair
150 for { let i } lt(i, lastIndex) { i := add(i, 0x01) } {
151 let offset := add(path.offset, shl(0x05, i))
152
153 let tokenIn := calldataload(offset)
154 let tokenOut := calldataload(add(offset, 0x20))
155 let zeroForOne := lt(tokenIn, tokenOut)
156
157 let reserveIn, reserveOut := getReserves(ptr, pair, zeroForOne)
158
159 // compute the amount of tokens to receive
160 let numerator := mul(mul(amountOut, 997), reserveOut)
161 let denominator := add(mul(reserveIn, 1000), mul(amountOut, 997))
162
163 amountOut := div(numerator, denominator)
164
165 switch eq(i, penultimateIndex)
166 case 0x00 {
167 let nextPair := pairFor(ptr, tokenOut, calldataload(add(offset, 0x40)))
168 swap(ptr, pair, zeroForOne, amountOut, nextPair)
169 pair := nextPair
170 }
171 default {
172 swap(ptr, pair, zeroForOne, amountOut, recipient)
173 break
174 }
175 }
176
177 require(iszero(lt(amountOut, amountOutMin)), 0xe52970aa) // InsufficientAmountOut()
178 }
179 }
180}
swapTokensForExactTokens
UniswapV2Router01.sol
1// SPDX-License-Identifier: MIT
2pragma solidity =0.6.6;
3
4contract UniswapV2Router01 {
5 function swapTokensForExactTokens(
6 uint amountOut,
7 uint amountInMax,
8 address[] calldata path,
9 address to,
10 uint deadline
11 ) external override ensure(deadline) returns (uint[] memory amounts) {
12 amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
13 require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
14 TransferHelper.safeTransferFrom(path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]);
15 _swap(amounts, path, to);
16 }
17
18 function _swap(uint[] memory amounts, address[] memory path, address _to) private {
19 for (uint i; i < path.length - 1; i++) {
20 (address input, address output) = (path[i], path[i + 1]);
21 (address token0,) = UniswapV2Library.sortTokens(input, output);
22 uint amountOut = amounts[i + 1];
23 (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
24 address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
25 IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(amount0Out, amount1Out, to, new bytes(0));
26 }
27 }
28}
V2Routing.sol
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.25;
3
4contract V2Routing {
5 function swapTokensForExactTokens(
6 Currency[] calldata path,
7 address recipient,
8 uint256 amountOut,
9 uint256 amountInMax,
10 uint256 deadline
11 ) external payable returns (uint256 amountIn) {
12 assembly ("memory-safe") {
13 function require(condition, selector) {
14 if iszero(condition) {
15 mstore(0x00, selector)
16 revert(0x1c, 0x04)
17 }
18 }
19
20 function ternary(condition, x, y) -> z {
21 z := xor(y, mul(xor(x, y), iszero(iszero(condition))))
22 }
23
24 function pairFor(ptr, token0, token1) -> pair {
25 // sort tokens if necessary
26 if gt(token0, token1) {
27 let temp := token0
28 token0 := token1
29 token1 := temp
30 }
31
32 // store the addresses of token0 and token1
33 mstore(ptr, shl(0x60, token0))
34 mstore(add(ptr, 0x14), shl(0x60, token1))
35
36 // compute the salt of the pair
37 let salt := keccak256(ptr, 0x28)
38
39 // store the address of the factory, computed salt, and the init code hash of the pair
40 mstore(ptr, add(hex"ff", shl(0x58, UNISWAP_V2_FACTORY)))
41 mstore(add(ptr, 0x15), salt)
42 mstore(add(ptr, 0x35), UNISWAP_V2_PAIR_INIT_CODE_HASH)
43
44 pair := and(keccak256(ptr, 0x55), 0xffffffffffffffffffffffffffffffffffffffff)
45
46 // revert if the pair at computed address is not deployed yet
47 require(iszero(iszero(extcodesize(pair))), 0x0022d46a) // PairNotExists()
48 }
49
50 function getReserves(ptr, pair, zeroForOne) -> reserveIn, reserveOut {
51 // fetch the reserves of the pair; swap positions if necessary
52 mstore(ptr, 0x0902f1ac00000000000000000000000000000000000000000000000000000000) // getReserves()
53
54 if iszero(staticcall(gas(), pair, ptr, 0x04, ptr, 0x40)) {
55 returndatacopy(ptr, 0x00, returndatasize())
56 revert(ptr, returndatasize())
57 }
58
59 switch zeroForOne
60 case 0x00 {
61 reserveOut := mload(ptr)
62 reserveIn := mload(add(ptr, 0x20))
63 }
64 default {
65 reserveIn := mload(ptr)
66 reserveOut := mload(add(ptr, 0x20))
67 }
68
69 require(and(iszero(iszero(reserveIn)), iszero(iszero(reserveOut))), 0x2f76b1d4) // InsuffcientReserves()
70 }
71
72 function swap(ptr, pair, zeroForOne, value, to) {
73 // encode the calldata with swap parameters, then perform the swap
74 mstore(ptr, 0x022c0d9f00000000000000000000000000000000000000000000000000000000) // swap(uint256,uint256,address,bytes)
75 mstore(add(ptr, 0x04), mul(value, iszero(zeroForOne)))
76 mstore(add(ptr, 0x24), mul(value, iszero(iszero(zeroForOne))))
77 mstore(add(ptr, 0x44), to)
78 mstore(add(ptr, 0x64), 0x80)
79
80 if iszero(call(gas(), pair, 0x00, ptr, 0xa4, 0x00, 0x00)) {
81 returndatacopy(ptr, 0x00, returndatasize())
82 revert(ptr, returndatasize())
83 }
84 }
85
86 require(iszero(lt(deadline, timestamp())), 0x1ab7da6b) // DeadlineExpired()
87 require(gt(path.length, 0x01), 0x20db8267) // InvalidPath()
88 require(iszero(iszero(amountInMax)), 0xdf5b2ee6) // InsufficientAmountIn()
89
90 // get a free memory pointer, then allocate memory
91 let ptr := mload(0x40)
92 mstore(0x40, add(ptr, 0xc0))
93
94 let lastIndex := sub(path.length, 0x01)
95 let penultimateIndex := sub(lastIndex, 0x01)
96
97 amountIn := amountOut
98
99 // iterate over the path array, computing the delta amounts for each pair
100 for { let i := lastIndex } gt(i, 0x00) { i := sub(i, 0x01) } {
101 let offset := add(path.offset, shl(0x05, i))
102
103 let tokenOut := calldataload(offset)
104 let tokenIn := calldataload(sub(offset, 0x20))
105 let pair := pairFor(ptr, tokenIn, tokenOut)
106 let zeroForOne := lt(tokenIn, tokenOut)
107
108 let reserveIn, reserveOut := getReserves(ptr, pair, zeroForOne)
109
110 // compute the amount of tokens to pay to the pair
111 let numerator := mul(mul(reserveIn, amountIn), 1000)
112 let denominator := mul(sub(reserveOut, amountIn), 997)
113
114 amountIn := add(div(numerator, denominator), 1)
115 }
116
117 require(iszero(gt(amountIn, amountInMax)), 0xdf5b2ee6) // InsufficientAmountIn()
118
119 // retrieve the address of the input token
120 let token := calldataload(path.offset)
121
122 // compute the address of the first pair
123 let pair := pairFor(ptr, token, calldataload(add(path.offset, 0x20)))
124
125 // wrap ETH before the iteration if the caller is paying with ETH; otherwise, pay the first pair with the input token
126 switch and(eq(token, WETH), iszero(lt(selfbalance(), amountIn)))
127 case 0x00 {
128 // revert if received any ETH
129 require(iszero(callvalue()), 0x21a64d90) // InvalidCallValue()
130
131 mstore(ptr, 0x23b872dd00000000000000000000000000000000000000000000000000000000) // transferFrom(address,address,uint256)
132 mstore(add(ptr, 0x04), caller())
133 mstore(add(ptr, 0x24), pair)
134 mstore(add(ptr, 0x44), amountIn)
135
136 if iszero(
137 and(
138 or(eq(mload(0x00), 0x01), iszero(returndatasize())),
139 call(gas(), token, 0x00, ptr, 0x64, 0x00, 0x20)
140 )
141 ) {
142 mstore(0x00, 0x7939f424) // TransferFromFailed()
143 revert(0x1c, 0x04)
144 }
145 }
146 default {
147 mstore(ptr, 0xd0e30db000000000000000000000000000000000000000000000000000000000) // deposit()
148
149 if iszero(call(gas(), WETH, amountIn, ptr, 0x04, 0x00, 0x00)) {
150 returndatacopy(ptr, 0x00, returndatasize())
151 revert(ptr, returndatasize())
152 }
153
154 mstore(ptr, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) // transfer(address,uint256)
155 mstore(add(ptr, 0x04), pair)
156 mstore(add(ptr, 0x24), amountIn)
157
158 if iszero(
159 and(
160 or(eq(mload(0x00), 0x01), iszero(returndatasize())),
161 call(gas(), WETH, 0x00, ptr, 0x44, 0x00, 0x20)
162 )
163 ) {
164 mstore(0x00, 0x90b8ec18) // TransferFailed()
165 revert(0x1c, 0x04)
166 }
167 }
168
169 amountOut := amountIn
170
171 // iterate over the path array, perform swap for each pair
172 for { let i } lt(i, lastIndex) { i := add(i, 0x01) } {
173 let offset := add(path.offset, shl(0x05, i))
174
175 let tokenIn := calldataload(offset)
176 let tokenOut := calldataload(add(offset, 0x20))
177 let zeroForOne := lt(tokenIn, tokenOut)
178
179 let reserveIn, reserveOut := getReserves(ptr, pair, zeroForOne)
180
181 // compute the amount of tokens to receive
182 let numerator := mul(mul(amountOut, 997), reserveOut)
183 let denominator := add(mul(reserveIn, 1000), mul(amountOut, 997))
184
185 amountOut := div(numerator, denominator)
186
187 switch eq(i, penultimateIndex)
188 case 0x00 {
189 let nextPair := pairFor(ptr, tokenOut, calldataload(add(offset, 0x40)))
190 swap(ptr, pair, zeroForOne, amountOut, nextPair)
191 pair := nextPair
192 }
193 default {
194 swap(ptr, pair, zeroForOne, amountOut, recipient)
195 break
196 }
197 }
198 }
199 }
200}
You can explore the full source code and tests here.