Skip to content

Commit 033273d

Browse files
committed
support affiliate fees in swap router
1 parent 12d3d45 commit 033273d

File tree

2 files changed

+189
-13
lines changed

2 files changed

+189
-13
lines changed

SwapRouter/src/SkipGoSwapRouter.sol

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,19 @@ contract SkipGoSwapRouter {
1313
bytes data;
1414
}
1515

16-
function swapExactIn(uint256 amountIn, uint256 amountOutMin, address tokenIn, address tokenOut, Hop[] calldata hops)
17-
external
18-
payable
19-
returns (uint256 amountOut)
20-
{
16+
struct Affiliate {
17+
address recipient;
18+
uint256 feeBPS;
19+
}
20+
21+
function swapExactIn(
22+
uint256 amountIn,
23+
uint256 amountOutMin,
24+
address tokenIn,
25+
address tokenOut,
26+
Hop[] calldata hops,
27+
Affiliate[] calldata affiliates
28+
) external payable returns (uint256 amountOut) {
2129
IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
2230

2331
amountOut = amountIn;
@@ -43,17 +51,20 @@ contract SkipGoSwapRouter {
4351

4452
require(amountOut >= amountOutMin, "amount out is less than amount out min");
4553

46-
IERC20(tokenOut).transfer(msg.sender, amountOut);
54+
uint256 amountPaid = _payAffiliateFees(tokenOut, amountOut, affiliates);
4755

48-
return amountOut;
56+
IERC20(tokenOut).transfer(msg.sender, amountOut - amountPaid);
57+
58+
amountOut = amountOut - amountPaid;
4959
}
5060

5161
function swapExactOut(
5262
uint256 amountOut,
5363
uint256 amountInMax,
5464
address tokenIn,
5565
address tokenOut,
56-
Hop[] calldata hops
66+
Hop[] calldata hops,
67+
Affiliate[] calldata affiliates
5768
) external payable returns (uint256 amountIn) {
5869
amountIn = getAmountIn(amountOut, hops);
5970

@@ -82,7 +93,9 @@ contract SkipGoSwapRouter {
8293
amountOut = abi.decode(returnData, (uint256));
8394
}
8495

85-
IERC20(tokenOut).transfer(msg.sender, amountOut);
96+
uint256 amountPaid = _payAffiliateFees(tokenOut, amountOut, affiliates);
97+
98+
IERC20(tokenOut).transfer(msg.sender, amountOut - amountPaid);
8699
}
87100

88101
function getAmountOut(uint256 amountIn, Hop[] calldata hops) public view returns (uint256 amountOut) {
@@ -129,4 +142,21 @@ contract SkipGoSwapRouter {
129142
revert(add(data, 32), mload(data))
130143
}
131144
}
145+
146+
function _payAffiliateFees(address token, uint256 amount, Affiliate[] calldata affiliates)
147+
private
148+
returns (uint256 amountPaid)
149+
{
150+
for (uint256 i = 0; i < affiliates.length; i++) {
151+
Affiliate memory affiliate = affiliates[i];
152+
153+
uint256 fee = (amount * affiliate.feeBPS) / 10000;
154+
155+
amountPaid += fee;
156+
157+
IERC20(token).transfer(affiliate.recipient, fee);
158+
}
159+
160+
return amountPaid;
161+
}
132162
}

SwapRouter/test/SkipGoSwapRouter.t.sol

Lines changed: 150 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ contract SkipGoSwapRouterTest is Test {
3232
address usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
3333
address wbtc = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599;
3434

35+
SkipGoSwapRouter.Affiliate[] memory affiliates = new SkipGoSwapRouter.Affiliate[](0);
36+
3537
SkipGoSwapRouter.Hop[] memory hops = new SkipGoSwapRouter.Hop[](2);
3638

3739
{
@@ -73,7 +75,7 @@ contract SkipGoSwapRouterTest is Test {
7375

7476
IERC20(weth).approve(address(router), amountIn);
7577

76-
uint256 amountOut = router.swapExactIn(amountIn, 1, weth, wbtc, hops);
78+
uint256 amountOut = router.swapExactIn(amountIn, 1, weth, wbtc, hops, affiliates);
7779

7880
vm.stopPrank();
7981

@@ -86,11 +88,82 @@ contract SkipGoSwapRouterTest is Test {
8688
assertEq(amountOut, expectedAmountOut);
8789
}
8890

91+
function test_swapExactIn_WithAffiliate() public {
92+
address weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
93+
address usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
94+
address wbtc = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599;
95+
96+
SkipGoSwapRouter.Hop[] memory hops = new SkipGoSwapRouter.Hop[](2);
97+
98+
{
99+
UniswapV2Adapter.UniswapV2Data memory hopOneData = UniswapV2Adapter.UniswapV2Data({
100+
pool: 0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc,
101+
zeroToOne: false,
102+
fee: 300
103+
});
104+
105+
bytes memory encodedHopOneData = abi.encode(hopOneData);
106+
107+
hops[0] = SkipGoSwapRouter.Hop({exchangeType: 1, data: encodedHopOneData});
108+
109+
UniswapV3Adapter.UniswapV3Data memory hopTwoData = UniswapV3Adapter.UniswapV3Data({
110+
tokenIn: usdc,
111+
tokenOut: wbtc,
112+
fee: 3000,
113+
quoter: 0x5e55C9e631FAE526cd4B0526C4818D6e0a9eF0e3,
114+
swapRouter: 0xE592427A0AEce92De3Edee1F18E0157C05861564
115+
});
116+
117+
bytes memory encodedHopTwoData = abi.encode(hopTwoData);
118+
119+
hops[1] = SkipGoSwapRouter.Hop({exchangeType: 2, data: encodedHopTwoData});
120+
}
121+
122+
uint256 amountIn = 1 ether;
123+
124+
address alice = makeAddr("alice");
125+
address bob = makeAddr("bob");
126+
127+
deal(weth, alice, amountIn);
128+
129+
SkipGoSwapRouter.Affiliate[] memory affiliates = new SkipGoSwapRouter.Affiliate[](1);
130+
131+
{
132+
affiliates[0] = SkipGoSwapRouter.Affiliate({recipient: bob, feeBPS: 100});
133+
}
134+
135+
uint256 expectedAmountOut = router.getAmountOut(amountIn, hops);
136+
137+
uint256 expectedAffiliateFee = (expectedAmountOut * 100) / 10000;
138+
139+
vm.startPrank(alice);
140+
141+
IERC20(weth).approve(address(router), amountIn);
142+
143+
uint256 amountOut = router.swapExactIn(amountIn, 1, weth, wbtc, hops, affiliates);
144+
145+
vm.stopPrank();
146+
147+
uint256 wethBalanceAfter = IERC20(weth).balanceOf(alice);
148+
uint256 wbtcBalanceAfter = IERC20(wbtc).balanceOf(alice);
149+
150+
uint256 bobWbtcBalanceAfter = IERC20(wbtc).balanceOf(bob);
151+
152+
assertEq(wethBalanceAfter, 0);
153+
assertEq(wbtcBalanceAfter, amountOut);
154+
155+
assertEq(amountOut, expectedAmountOut - expectedAffiliateFee);
156+
157+
assertEq(bobWbtcBalanceAfter, expectedAffiliateFee);
158+
}
159+
89160
function test_revertSwapExactIn_WhenAmountOutIsLessThanAmountIn() public {
90161
address weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
91162
address usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
92163
address wbtc = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599;
93164

165+
SkipGoSwapRouter.Affiliate[] memory affiliates = new SkipGoSwapRouter.Affiliate[](0);
166+
94167
SkipGoSwapRouter.Hop[] memory hops = new SkipGoSwapRouter.Hop[](2);
95168

96169
{
@@ -130,7 +203,7 @@ contract SkipGoSwapRouterTest is Test {
130203
IERC20(weth).approve(address(router), amountIn);
131204

132205
vm.expectRevert("amount out is less than amount out min");
133-
router.swapExactIn(amountIn, expectedAmountOut + 1, weth, wbtc, hops);
206+
router.swapExactIn(amountIn, expectedAmountOut + 1, weth, wbtc, hops, affiliates);
134207

135208
vm.stopPrank();
136209
}
@@ -140,6 +213,8 @@ contract SkipGoSwapRouterTest is Test {
140213
address usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
141214
address wbtc = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599;
142215

216+
SkipGoSwapRouter.Affiliate[] memory affiliates = new SkipGoSwapRouter.Affiliate[](0);
217+
143218
SkipGoSwapRouter.Hop[] memory hops = new SkipGoSwapRouter.Hop[](2);
144219

145220
{
@@ -181,7 +256,7 @@ contract SkipGoSwapRouterTest is Test {
181256

182257
IERC20(weth).approve(address(router), expectedAmountIn);
183258

184-
uint256 amountIn = router.swapExactOut(amountOut, type(uint256).max, weth, wbtc, hops);
259+
uint256 amountIn = router.swapExactOut(amountOut, type(uint256).max, weth, wbtc, hops, affiliates);
185260

186261
vm.stopPrank();
187262

@@ -194,11 +269,82 @@ contract SkipGoSwapRouterTest is Test {
194269
assertEq(amountIn, expectedAmountIn);
195270
}
196271

272+
function test_swapExactOut_WithAffiliate() public {
273+
address weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
274+
address usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
275+
address wbtc = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599;
276+
277+
SkipGoSwapRouter.Hop[] memory hops = new SkipGoSwapRouter.Hop[](2);
278+
279+
{
280+
UniswapV2Adapter.UniswapV2Data memory hopOneData = UniswapV2Adapter.UniswapV2Data({
281+
pool: 0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc,
282+
zeroToOne: false,
283+
fee: 300
284+
});
285+
286+
bytes memory encodedHopOneData = abi.encode(hopOneData);
287+
288+
hops[0] = SkipGoSwapRouter.Hop({exchangeType: 1, data: encodedHopOneData});
289+
290+
UniswapV3Adapter.UniswapV3Data memory hopTwoData = UniswapV3Adapter.UniswapV3Data({
291+
tokenIn: usdc,
292+
tokenOut: wbtc,
293+
fee: 3000,
294+
quoter: 0x5e55C9e631FAE526cd4B0526C4818D6e0a9eF0e3,
295+
swapRouter: 0xE592427A0AEce92De3Edee1F18E0157C05861564
296+
});
297+
298+
bytes memory encodedHopTwoData = abi.encode(hopTwoData);
299+
300+
hops[1] = SkipGoSwapRouter.Hop({exchangeType: 2, data: encodedHopTwoData});
301+
}
302+
303+
uint256 amountOut = 100000000;
304+
305+
address alice = makeAddr("alice");
306+
address bob = makeAddr("bob");
307+
308+
uint256 expectedAmountIn = router.getAmountIn(amountOut, hops);
309+
310+
deal(weth, alice, expectedAmountIn);
311+
312+
SkipGoSwapRouter.Affiliate[] memory affiliates = new SkipGoSwapRouter.Affiliate[](1);
313+
314+
{
315+
affiliates[0] = SkipGoSwapRouter.Affiliate({recipient: bob, feeBPS: 100});
316+
}
317+
318+
vm.startPrank(alice);
319+
320+
IERC20(weth).approve(address(router), expectedAmountIn);
321+
322+
uint256 amountIn = router.swapExactOut(amountOut, type(uint256).max, weth, wbtc, hops, affiliates);
323+
324+
vm.stopPrank();
325+
326+
uint256 expectedAffiliateFee = (amountOut * 100) / 10000;
327+
328+
uint256 wethBalanceAfter = IERC20(weth).balanceOf(alice);
329+
uint256 wbtcBalanceAfter = IERC20(wbtc).balanceOf(alice);
330+
331+
uint256 bobWbtcBalanceAfter = IERC20(wbtc).balanceOf(bob);
332+
333+
assertEq(wethBalanceAfter, 0);
334+
assertEq(wbtcBalanceAfter, amountOut - expectedAffiliateFee);
335+
336+
assertEq(amountIn, expectedAmountIn);
337+
338+
assertEq(bobWbtcBalanceAfter, expectedAffiliateFee);
339+
}
340+
197341
function test_revertSwapExactOut_WhenAmountInIsGreaterThanAmountInMax() public {
198342
address weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
199343
address usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
200344
address wbtc = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599;
201345

346+
SkipGoSwapRouter.Affiliate[] memory affiliates = new SkipGoSwapRouter.Affiliate[](0);
347+
202348
SkipGoSwapRouter.Hop[] memory hops = new SkipGoSwapRouter.Hop[](2);
203349

204350
{
@@ -238,7 +384,7 @@ contract SkipGoSwapRouterTest is Test {
238384
IERC20(weth).approve(address(router), expectedAmountIn);
239385

240386
vm.expectRevert("amount in is greater than amount in max");
241-
router.swapExactOut(amountOut, expectedAmountIn - 1, weth, wbtc, hops);
387+
router.swapExactOut(amountOut, expectedAmountIn - 1, weth, wbtc, hops, affiliates);
242388

243389
vm.stopPrank();
244390
}

0 commit comments

Comments
 (0)