@@ -40,26 +40,43 @@ contract TrebleSwapTest is BaseTest {
40
40
UserOperation userOp;
41
41
SolverOperation[] solverOps;
42
42
DAppOperation dAppOp;
43
+ uint256 blockBefore; // block before real tx happened
44
+ bool nativeInput;
45
+ bool nativeOutput;
43
46
}
44
47
45
- TrebleSwapDAppControl public trebleSwapControl;
46
- Sig public sig;
47
- Args public args;
48
+ struct BeforeAndAfterVars {
49
+ uint256 userInputTokenBalance;
50
+ uint256 userOutputTokenBalance;
51
+ uint256 solverInputTokenBalance;
52
+ uint256 solverOutputTokenBalance;
53
+ uint256 burnAddressTrebBalance;
54
+ uint256 atlasGasSurcharge;
55
+ }
48
56
49
57
// Odos Router v2 on Base
50
- address public constant ODOS_ROUTER = 0x19cEeAd7105607Cd444F5ad10dd51356436095a1 ;
58
+ address constant ODOS_ROUTER = 0x19cEeAd7105607Cd444F5ad10dd51356436095a1 ;
59
+ address constant ETH = address (0 );
60
+ address constant BURN = address (0xdead );
51
61
52
62
// Base ERC20 addresses
53
63
IERC20 bWETH = IERC20 (0x4200000000000000000000000000000000000006 );
54
64
IERC20 USDC = IERC20 (0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 );
55
65
IERC20 WUF = IERC20 (0x4da78059D97f155E18B37765e2e042270f4E0fC4 );
56
- IERC20 TREB = IERC20 (0x4ed4E862860beD51a9570b96d89aF5E1B0Efefed );
66
+ IERC20 TREB = IERC20 (0x4ed4E862860beD51a9570b96d89aF5E1B0Efefed ); // TODO DEGEN for now, replace when TREB available
57
67
68
+ TrebleSwapDAppControl trebleSwapControl;
58
69
address executionEnvironment;
59
70
71
+ Sig sig;
72
+ Args args;
73
+ SwapTokenInfo swapInfo;
74
+ BeforeAndAfterVars beforeVars;
75
+
60
76
function setUp () public virtual override {
61
77
// Fork Base
62
78
vm.createSelectFork (vm.envString ("BASE_RPC_URL " ), 18_906_794 );
79
+ // TODO make this before all tx blocks in this file - atlas deployed before
63
80
64
81
_BaseTest_DeployAtlasContracts ();
65
82
@@ -82,99 +99,160 @@ contract TrebleSwapTest is BaseTest {
82
99
vm.label (address (TREB), "DEGEN " ); // TODO change label to TREB when TREB token available
83
100
}
84
101
85
- function testTrebleSwap_swapUsdcToWuf () public {
86
- // Based on: https://basescan.org/tx/0x0ef4a9c24bbede2b39e12f5e5417733fa8183f372e41ee099c2c7523064c1b55
87
- // Swaps 197.2 USDC for 200,064,568.9293 WUF
102
+ // ---------------------------------------------------- //
103
+ // Scenario Tests //
104
+ // ---------------------------------------------------- //
105
+
106
+ function testTrebleSwap_Metacall_Erc20ToErc20_ZeroSolvers () public {
107
+ // Tx: https://basescan.org/tx/0x0ef4a9c24bbede2b39e12f5e5417733fa8183f372e41ee099c2c7523064c1b55
108
+ // Swaps 197.2 USDC for at least 198,080,836.0295 WUF
109
+
110
+ args.blockBefore = 18_906_794 ;
111
+ args.nativeInput = false ;
112
+ args.nativeOutput = false ;
113
+ swapInfo = SwapTokenInfo ({
114
+ inputToken: address (USDC),
115
+ inputAmount: 197_200_000 ,
116
+ outputToken: address (WUF),
117
+ outputMin: 1_980_808_360_295
118
+ });
119
+ vm.roll (args.blockBefore);
120
+
121
+ // Modify swapCompact() calldata to replace original caller (0xa4a9220dE44D699f453DdF7F7630A96cDEdf6463) with
122
+ // user's Execution Environment address:
123
+ bytes memory calldataPart1 =
124
+ hex "83bd37f9000400014da78059d97f155e18b37765e2e042270f4e0fc4040bc108800601d1d9f50a5a028f5c0001f73f77f9466da712590ae432a80f07fd50a7de600001616535324976f8dbcef19df0705b95ace86ebb480001 " ;
125
+ bytes memory calldataPart2 =
126
+ hex "0000000006020207003401000001020180000005020a0004040500000301010003060119ff0000000000000000000000000000000000000000000000000000000000000000616535324976f8dbcef19df0705b95ace86ebb48833589fcd6edb6e08f4c7c32d4f71b54bda02913569d81c17b5b4ac08929dc1769b8e39668d3ae29f6c0a374a483101e04ef5f7ac9bd15d9142bac95d9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca42000000000000000000000000000000000000060000000000000000 " ;
127
+ bytes memory swapCompactCalldata = abi.encodePacked (calldataPart1, executionEnvironment, calldataPart2);
128
+
129
+ _checkActualCalldataMatchesExpected (swapCompactCalldata);
130
+ _buildUserOp (swapCompactCalldata);
131
+ // no solverOps
132
+ _buildAndSignDAppOp ();
133
+ _setBalancesAndApprovals ();
134
+ _checkSimulationsPass ();
135
+ _doMetacallAndChecks ({ winningSolverEOA: address (0 ), winningSolver: address (0 ) });
136
+ }
88
137
89
- // Fork Base at block before USDC -> WUF swap on Odos
138
+ function testTrebleSwap_Metacall_EthToErc20_ZeroSolvers () public {
139
+ vm.skip (true );
90
140
91
- // USDC: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
92
- // WUF: 0x4da78059D97f155E18B37765e2e042270f4E0fC4
141
+ // Tx 1: https://basescan.org/tx/0xdc4f046b052cfaf227ccb1ad83b4a86521cf4f2bcf5343793f22fc39f61dfe02
93
142
94
- // Original calldata from tx above:
95
- // 0x83bd37f9 -> swapCompact selector
96
- // 0004 -> input token -> address list [USDC]
97
- // 0001 4da78059d97f155e18b37765e2e042270f4e0fc4 -> output token -> calldata address [WUF]
98
- // 04 0bc10880 -> input amount -> length = 4 bytes, value = 197200000
99
- // 0601d1d9f50a5a028f5c0001f73f77f9466da712590ae432a80f07fd50a7de600001616535324976f8dbcef19df0705b95ace86ebb480001a4a9220de44d699f453ddf7f7630a96cdedf64630000000006020207003401000001020180000005020a0004040500000301010003060119ff0000000000000000000000000000000000000000000000000000000000000000616535324976f8dbcef19df0705b95ace86ebb48833589fcd6edb6e08f4c7c32d4f71b54bda02913569d81c17b5b4ac08929dc1769b8e39668d3ae29f6c0a374a483101e04ef5f7ac9bd15d9142bac95d9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca42000000000000000000000000000000000000060000000000000000
143
+ // swapCompact calldata:
144
+ // 83bd37f900000004072386f26fc10000040180ef410147ae0001f73f77f9466da712590ae432a80f07fd50a7de60000000013eb8b2f4584c642a43ed5cad2f83182de41b5de2000000010301020300040101020a0001010201ff000000000000000000000000000000000074cb6260be6f31965c239df6d6ef2ac2b5d4f0204200000000000000000000000000000000000006000000000000000000000000000000000000000000000000
100
145
101
- // Replace caller address (0xa4a9220dE44D699f453DdF7F7630A96cDEdf6463)
102
- // with EE address: (0x31b9EdCc7937b5d5E434D45aaF1F4f98407f45eb)
103
- // Build userOp.data for swapCompact call
104
- bytes memory swapCompactCalldata =
105
- hex "83bd37f9000400014da78059d97f155e18b37765e2e042270f4e0fc4040bc108800601d1d9f50a5a028f5c0001f73f77f9466da712590ae432a80f07fd50a7de600001616535324976f8dbcef19df0705b95ace86ebb48000131b9EdCc7937b5d5E434D45aaF1F4f98407f45eb0000000006020207003401000001020180000005020a0004040500000301010003060119ff0000000000000000000000000000000000000000000000000000000000000000616535324976f8dbcef19df0705b95ace86ebb48833589fcd6edb6e08f4c7c32d4f71b54bda02913569d81c17b5b4ac08929dc1769b8e39668d3ae29f6c0a374a483101e04ef5f7ac9bd15d9142bac95d9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca42000000000000000000000000000000000000060000000000000000 " ;
146
+ // From: 0x3EB8b2F4584c642a43eD5caD2F83182de41B5dE2
106
147
107
- bytes memory encodedCall =
108
- abi.encodePacked (TrebleSwapDAppControl.decodeUserOpData.selector , swapCompactCalldata);
109
- (bool res , bytes memory returnData ) = address (trebleSwapControl).staticcall (encodedCall);
148
+ // Tx 2: https://basescan.org/tx/0xe138def4155bea056936038b9374546a366828ab8bf1233056f9e2fe4c6af999
110
149
111
- console.log ("res " , res);
150
+ // swapCompact calldata:
151
+ // 0x83bd37f9000000050801b505fc9226ffb80910d5345f06a9650000028f5c0001f73f77f9466da712590ae432a80f07fd50a7de6000000001adf6918ed87a5d7ae334bb42ca2d98971b527306000000000401020500040101020a000202030100340101000104007ffffffaff00000000005fb33b095c6e739be19364ab408cd8f102262bb672ab388e2e2f6facef59e3c3fa2c4e29011c2d384200000000000000000000000000000000000006833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000
112
152
113
- SwapTokenInfo memory swapInfo = abi.decode (returnData, (SwapTokenInfo));
153
+ // From: 0xadF6918eD87a5D7aE334bB42Ca2d98971B527306
154
+ }
114
155
115
- // SwapTokenInfo memory swapInfo = decodeSwapCompactCalldata(swapCompactCalldata);
156
+ // ---------------------------------------------------- //
157
+ // Helper Functions //
158
+ // ---------------------------------------------------- //
116
159
117
- console.log ("swapInfo.inputToken " , swapInfo.inputToken);
118
- console.log ("swapInfo.inputAmount " , swapInfo.inputAmount);
119
- console.log ("swapInfo.outputToken " , swapInfo.outputToken);
120
- console.log ("swapInfo.outputMin " , swapInfo.outputMin);
160
+ function _doMetacallAndChecks (address winningSolverEOA , address winningSolver ) internal {
161
+ bool auctionWonExpected = winningSolver != address (0 );
162
+ beforeVars.userInputTokenBalance = _balanceOf (swapInfo.inputToken, userEOA);
163
+ beforeVars.userOutputTokenBalance = _balanceOf (swapInfo.outputToken, userEOA);
164
+ beforeVars.solverInputTokenBalance = _balanceOf (swapInfo.inputToken, winningSolverEOA);
165
+ beforeVars.solverOutputTokenBalance = _balanceOf (swapInfo.outputToken, winningSolverEOA);
166
+ beforeVars.burnAddressTrebBalance = _balanceOf (address (TREB), BURN);
167
+ beforeVars.atlasGasSurcharge = atlas.cumulativeSurcharge ();
121
168
122
- console.log ("gas price: " , tx .gasprice );
169
+ vm.prank (userEOA);
170
+ bool auctionWon = atlas.metacall { value: 1e17 }(args.userOp, args.solverOps, args.dAppOp);
123
171
124
- // Build userOp
172
+ assertEq (auctionWon, auctionWonExpected, "auctionWon not as expected " );
173
+
174
+ // Check user balance changes
175
+ assertEq (
176
+ _balanceOf (swapInfo.inputToken, userEOA),
177
+ beforeVars.userInputTokenBalance - swapInfo.inputAmount,
178
+ "wrong user input token balance change "
179
+ );
180
+ assertTrue (
181
+ _balanceOf (swapInfo.outputToken, userEOA) >= beforeVars.userOutputTokenBalance + swapInfo.outputMin,
182
+ "wrong user output token balance change "
183
+ );
184
+ }
185
+
186
+ function _checkSimulationsPass () internal {
187
+ // TODO do all variations of sim calls here with checks
188
+ }
189
+
190
+ function _checkActualCalldataMatchesExpected (bytes memory userOpData ) internal {
191
+ bytes memory encodedCall = abi.encodePacked (TrebleSwapDAppControl.decodeUserOpData.selector , userOpData);
192
+ (bool res , bytes memory returnData ) = address (trebleSwapControl).staticcall (encodedCall);
193
+ assertEq (res, true , "calldata check failed in decode call " );
194
+
195
+ SwapTokenInfo memory decodedInfo = abi.decode (returnData, (SwapTokenInfo));
196
+ assertEq (decodedInfo.inputToken, swapInfo.inputToken, "inputToken mismatch " );
197
+ assertEq (decodedInfo.inputAmount, swapInfo.inputAmount, "inputAmount mismatch " );
198
+ assertEq (decodedInfo.outputToken, swapInfo.outputToken, "outputToken mismatch " );
199
+ assertEq (decodedInfo.outputMin, swapInfo.outputMin, "outputMin mismatch " );
200
+ }
201
+
202
+ function _setBalancesAndApprovals () internal {
203
+ // User input token and Atlas approval
204
+ if (args.nativeInput) {
205
+ deal (userEOA, swapInfo.inputAmount);
206
+ } else {
207
+ deal (swapInfo.inputToken, userEOA, swapInfo.inputAmount);
208
+ vm.prank (userEOA);
209
+ IERC20 (swapInfo.inputToken).approve (address (atlas), swapInfo.inputAmount);
210
+ }
211
+
212
+ // TODO give solver contracts TREB for bids
213
+ }
214
+
215
+ function _buildUserOp (bytes memory userOpData ) internal {
125
216
args.userOp = UserOperation ({
126
217
from: userEOA,
127
218
to: address (atlas),
128
- value: 0 ,
219
+ value: args.nativeInput ? swapInfo.inputAmount : 0 ,
129
220
gas: 1_000_000 ,
130
221
maxFeePerGas: tx .gasprice ,
131
222
nonce: 1 ,
132
- deadline: 18_906_796 , // 1 block after tx happened
223
+ deadline: args.blockBefore + 2 ,
133
224
dapp: ODOS_ROUTER,
134
225
control: address (trebleSwapControl),
135
226
callConfig: trebleSwapControl.CALL_CONFIG (),
136
227
sessionKey: address (0 ),
137
- data: swapCompactCalldata,
138
- signature: new bytes (0 ) // no sig - user will be bundler
139
- });
140
-
141
- // No solverOp for now
228
+ data: userOpData,
229
+ signature: new bytes (0 )
230
+ });
231
+ }
142
232
143
- // Build DAppOperation
233
+ function _buildAndSignDAppOp () internal {
144
234
args.dAppOp = DAppOperation ({
145
235
from: governanceEOA,
146
236
to: address (atlas),
147
237
nonce: 1 ,
148
- deadline: 18_906_796 , // 1 block after tx happened
238
+ deadline: args.blockBefore + 2 ,
149
239
control: address (trebleSwapControl),
150
240
bundler: address (0 ),
151
241
userOpHash: atlasVerification.getUserOperationHash (args.userOp),
152
242
callChainHash: CallVerification.getCallChainHash (args.userOp, args.solverOps),
153
243
signature: new bytes (0 )
154
244
});
155
245
156
- // Gov signs dAppOp
157
246
(sig.v, sig.r, sig.s) = vm.sign (governancePK, atlasVerification.getDAppOperationPayload (args.dAppOp));
158
247
args.dAppOp.signature = abi.encodePacked (sig.r, sig.s, sig.v);
248
+ }
159
249
160
- // Mint userEOA enough USDC
161
- deal (address (USDC), userEOA, swapInfo.inputAmount);
162
- assertEq (USDC.balanceOf (userEOA), swapInfo.inputAmount);
163
-
164
- console.log ("User balances before: " );
165
- console.log ("USDC " , USDC.balanceOf (userEOA));
166
- console.log ("WUF " , WUF.balanceOf (userEOA));
167
-
168
- // User approves Atlas to take input token - USDC
169
- vm.startPrank (userEOA);
170
- USDC.approve (address (atlas), swapInfo.inputAmount);
171
-
172
- bool auctionWon = atlas.metacall { value: 1e17 }(args.userOp, args.solverOps, args.dAppOp);
173
- vm.stopPrank ();
174
-
175
- console.log ("User balances after: " );
176
- console.log ("USDC " , USDC.balanceOf (userEOA));
177
- console.log ("WUF " , WUF.balanceOf (userEOA));
250
+ function _balanceOf (address token , address account ) internal view returns (uint256 ) {
251
+ if (token == ETH) {
252
+ return account.balance;
253
+ } else {
254
+ return IERC20 (token).balanceOf (account);
255
+ }
178
256
}
179
257
}
180
258
0 commit comments