Skip to content

Commit f51f0b1

Browse files
committed
feat: Treble calldata decoder and preOps hook done
1 parent d7d3f8d commit f51f0b1

File tree

2 files changed

+109
-8
lines changed

2 files changed

+109
-8
lines changed

src/contracts/examples/trebleswap/TrebleSwapDAppControl.sol

Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,23 @@ import "src/contracts/types/SolverOperation.sol";
1616
// ODOS v2 Router on Base: https://basescan.org/address/0x19ceead7105607cd444f5ad10dd51356436095a1
1717
// Main function: swapCompact()
1818

19+
struct SwapTokenInfo {
20+
address inputToken;
21+
uint256 inputAmount;
22+
address outputToken;
23+
uint256 outputMin;
24+
}
25+
1926
contract TrebleSwapDAppControl is DAppControl {
27+
address public constant ODOS_ROUTER = 0x19cEeAd7105607Cd444F5ad10dd51356436095a1;
28+
2029
// TODO WETH on Base for now, change to TREB when token deployed
2130
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();
2336

2437
constructor(
2538
address atlas
@@ -58,10 +71,24 @@ contract TrebleSwapDAppControl is DAppControl {
5871
// ---------------------------------------------------- //
5972

6073
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.
6592
}
6693

6794
// UserOperation happens here. EE calls router (userOp.dapp) with userOp.data as calldata.
@@ -83,4 +110,78 @@ contract TrebleSwapDAppControl is DAppControl {
83110
function getBidValue(SolverOperation calldata solverOp) public view virtual override returns (uint256) {
84111
return solverOp.bidAmount;
85112
}
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+
}
86187
}

test/TrebleSwap.t.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ contract TrebleSwapTest is BaseTest {
9191
hex"83bd37f9000400014da78059d97f155e18b37765e2e042270f4e0fc4040bc108800601d1d9f50a5a028f5c0001f73f77f9466da712590ae432a80f07fd50a7de600001616535324976f8dbcef19df0705b95ace86ebb480001736F6980876FDa51A610AB79E2856528a62Bf80e0000000006020207003401000001020180000005020a0004040500000301010003060119ff0000000000000000000000000000000000000000000000000000000000000000616535324976f8dbcef19df0705b95ace86ebb48833589fcd6edb6e08f4c7c32d4f71b54bda02913569d81c17b5b4ac08929dc1769b8e39668d3ae29f6c0a374a483101e04ef5f7ac9bd15d9142bac95d9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca42000000000000000000000000000000000000060000000000000000";
9292

9393
bytes memory encodedCall = abi.encodePacked(this.decodeSwapCompactCalldata.selector, swapCompactCalldata);
94-
(bool res, bytes memory returnData) = address(this).call(encodedCall);
94+
(bool res, bytes memory returnData) = address(this).staticcall(encodedCall);
9595

9696
console.log("res", res);
9797

@@ -130,7 +130,7 @@ contract TrebleSwapTest is BaseTest {
130130
// }
131131

132132
// TODO once this works, move to TrebleSwapDAppControl
133-
function decodeSwapCompactCalldata() public returns (SwapTokenInfo memory swapTokenInfo) {
133+
function decodeSwapCompactCalldata() public view returns (SwapTokenInfo memory swapTokenInfo) {
134134
assembly {
135135
// helper function to get address either from storage or calldata
136136
function getAddress(currPos) -> result, newPos {
@@ -156,7 +156,7 @@ contract TrebleSwapTest is BaseTest {
156156
let success :=
157157
staticcall(
158158
gas(), // gas remaining
159-
0x19cEeAd7105607Cd444F5ad10dd51356436095a1, // Odos v2 Router on Base
159+
ODOS_ROUTER,
160160
ptr, // input location
161161
0x24, // input size (4 byte selector + uint256 arg)
162162
ptr, // output location

0 commit comments

Comments
 (0)