Routing in Assembly

Routing in Assembly

Solidity
Uniswap
Yul
1m

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.


Explore more posts