@@ -16,10 +16,23 @@ import "src/contracts/types/SolverOperation.sol";
16
16
// ODOS v2 Router on Base: https://basescan.org/address/0x19ceead7105607cd444f5ad10dd51356436095a1
17
17
// Main function: swapCompact()
18
18
19
+ struct SwapTokenInfo {
20
+ address inputToken;
21
+ uint256 inputAmount;
22
+ address outputToken;
23
+ uint256 outputMin;
24
+ }
25
+
19
26
contract TrebleSwapDAppControl is DAppControl {
27
+ address public constant ODOS_ROUTER = 0x19cEeAd7105607Cd444F5ad10dd51356436095a1 ;
28
+
20
29
// TODO WETH on Base for now, change to TREB when token deployed
21
30
address public constant TREB = address (0x4200000000000000000000000000000000000006 );
22
- address internal constant _NATIVE_TOKEN = address (0 );
31
+ address internal constant _ETH = address (0 );
32
+
33
+ error InvalidUserOpData ();
34
+ error UserOpDappNotOdosRouter ();
35
+ error InsufficientUserOpValue ();
23
36
24
37
constructor (
25
38
address atlas
@@ -58,10 +71,24 @@ contract TrebleSwapDAppControl is DAppControl {
58
71
// ---------------------------------------------------- //
59
72
60
73
function _preOpsCall (UserOperation calldata userOp ) internal virtual override returns (bytes memory ) {
61
- // User approves Atlas before tx, so pull tokens via Permit69 here, to be used in UserOperation.
62
- // Use preOps hook to extract swap data (tokenIn, tokenOut, amounts)
63
- // Validate userOp.data is correct format for UserOperation call to router
64
- // Return extracted swap data for safety checks in allocateValue
74
+ if (userOp.dapp != ODOS_ROUTER) revert UserOpDappNotOdosRouter ();
75
+
76
+ (bool success , bytes memory data ) =
77
+ CONTROL.staticcall (abi.encodePacked (this .decodeUserOpData.selector , userOp.data));
78
+
79
+ if (! success) revert InvalidUserOpData ();
80
+
81
+ SwapTokenInfo memory swapTokenInfo = abi.decode (data, (SwapTokenInfo));
82
+
83
+ // If inputToken is ERC20, transfer tokens from user to EE, and approve Odos router for swap
84
+ if (swapTokenInfo.inputToken != _ETH) {
85
+ _transferUserERC20 (swapTokenInfo.inputToken, address (this ), swapTokenInfo.inputAmount);
86
+ SafeTransferLib.safeApprove (swapTokenInfo.inputToken, ODOS_ROUTER, swapTokenInfo.inputAmount);
87
+ } else {
88
+ if (userOp.value < swapTokenInfo.inputAmount) revert InsufficientUserOpValue ();
89
+ }
90
+
91
+ return data; // return SwapTokenInfo in bytes format, to be used in allocateValue.
65
92
}
66
93
67
94
// UserOperation happens here. EE calls router (userOp.dapp) with userOp.data as calldata.
@@ -83,4 +110,78 @@ contract TrebleSwapDAppControl is DAppControl {
83
110
function getBidValue (SolverOperation calldata solverOp ) public view virtual override returns (uint256 ) {
84
111
return solverOp.bidAmount;
85
112
}
113
+
114
+ // Call this helper with userOp.data appended as calldata, to decode swap info in the Odos calldata.
115
+ function decodeUserOpData () public view returns (SwapTokenInfo memory swapTokenInfo ) {
116
+ assembly {
117
+ // helper function to get address either from calldata or Odos router addressList()
118
+ function getAddress (currPos) -> result, newPos {
119
+ let inputPos := shr (240 , calldataload (currPos))
120
+
121
+ switch inputPos
122
+ // Reserve the null address as a special case that can be specified with 2 null bytes
123
+ case 0x0000 { newPos := add (currPos, 2 ) }
124
+ // This case means that the address is encoded in the calldata directly following the code
125
+ case 0x0001 {
126
+ result := and (shr (80 , calldataload (currPos)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF )
127
+ newPos := add (currPos, 22 )
128
+ }
129
+ // If not 0000 or 0001, call ODOS_ROUTER.addressList(inputPos - 2) to get address
130
+ default {
131
+ // 0000 and 0001 are reserved for cases above, so offset by 2 for addressList index
132
+ let arg := sub (inputPos, 2 )
133
+ let selector := 0xb810fb43 // function selector for "addressList(uint256)"
134
+ let ptr := mload (0x40 ) // get the free memory pointer
135
+ mstore (ptr, shl (224 , selector)) // shift selector to left of slot and store
136
+ mstore (add (ptr, 4 ), arg) // store the uint256 argument after the selector
137
+
138
+ // Perform the external call
139
+ let success :=
140
+ staticcall (
141
+ gas (), // gas remaining
142
+ ODOS_ROUTER,
143
+ ptr, // input location
144
+ 0x24 , // input size (4 byte selector + uint256 arg)
145
+ ptr, // output location
146
+ 0x20 // output size (32 bytes for the address)
147
+ )
148
+
149
+ if eq (success, 0 ) { revert (0 , 0 ) }
150
+
151
+ result := mload (ptr)
152
+ newPos := add (currPos, 2 )
153
+ }
154
+ }
155
+
156
+ let result := 0
157
+ let pos := 8 // skip Odos compactSwap selector and this helper selector (4 + 4 bytes)
158
+
159
+ // swapTokenInfo.inputToken (slot 0)
160
+ result, pos := getAddress (pos)
161
+ mstore (swapTokenInfo, result)
162
+
163
+ // swapTokenInfo.outputToken (slot 2)
164
+ result, pos := getAddress (pos)
165
+ mstore (add (swapTokenInfo, 0x40 ), result)
166
+
167
+ // swapTokenInfo.inputAmount (slot 1)
168
+ let inputAmountLength := shr (248 , calldataload (pos))
169
+ pos := add (pos, 1 )
170
+ if inputAmountLength {
171
+ mstore (add (swapTokenInfo, 0x20 ), shr (mul (sub (32 , inputAmountLength), 8 ), calldataload (pos)))
172
+ pos := add (pos, inputAmountLength)
173
+ }
174
+
175
+ // swapTokenInfo.outputMin (slot 3)
176
+ // get outputQuote and slippageTolerance from calldata, then calculate outputMin
177
+ let quoteAmountLength := shr (248 , calldataload (pos))
178
+ pos := add (pos, 1 )
179
+ let outputQuote := shr (mul (sub (32 , quoteAmountLength), 8 ), calldataload (pos))
180
+ pos := add (pos, quoteAmountLength)
181
+ {
182
+ let slippageTolerance := shr (232 , calldataload (pos))
183
+ mstore (add (swapTokenInfo, 0x60 ), div (mul (outputQuote, sub (0xFFFFFF , slippageTolerance)), 0xFFFFFF ))
184
+ }
185
+ }
186
+ }
86
187
}
0 commit comments