package calldata import ( "encoding/hex" "encoding/json" "os" "path/filepath" "strings" "testing" ) type payloadFixture struct { Protocol string `json:"protocol"` Function string `json:"function"` FunctionSig string `json:"function_sig"` InputData string `json:"input_data"` } func loadPayload(t *testing.T, name string) payloadFixture { t.Helper() fullPath := filepath.Join("testdata", "payloads", name) raw, err := os.ReadFile(fullPath) if err != nil { t.Fatalf("failed to read payload fixture %s: %v", name, err) } var pf payloadFixture if err := json.Unmarshal(raw, &pf); err != nil { t.Fatalf("failed to unmarshal payload %s: %v", name, err) } return pf } func decodeDirect(raw []byte, ctx *MulticallContext) []*SwapCall { validator := getAddressValidator() return decodeSwapCallRecursive(raw, ctx, validator, 0) } func TestCapturedPayloadDecoding(t *testing.T) { cases := []struct { name string fixture string expectSwaps int expectSelector string tokenIn string tokenOut string verifyProtocol string isMulticall bool }{ { name: "uniswap_v3_exact_output_single", fixture: "uniswapv3_exact_output_single.json", expectSwaps: 1, expectSelector: "db3e2198", tokenIn: "0xff970a61a04b1ca14834a43f5de4533ebddb5cc8", tokenOut: "0x912ce59144191c1204e64559fe8253a0e49e6548", verifyProtocol: "UniswapV3", }, { name: "uniswap_v3_exact_input_single", fixture: "uniswapv3_exact_input_single.json", expectSwaps: 1, expectSelector: "414bf389", tokenIn: "0x82af49447d8a07e3bd95bd0d56f35241523fbab1", tokenOut: "0x440017a1b021006d556d7fc06a54c32e42eb745b", verifyProtocol: "UniswapV3", }, { name: "uniswap_v2_swap_exact_tokens", fixture: "uniswapv2_exact_tokens.json", expectSwaps: 1, expectSelector: "38ed1739", tokenIn: "0x03f6921f6e948016631ce796331294d5a863a9ee", tokenOut: "0xdcc9691793633176acf5cfdc1a658cb3b982e2fb", verifyProtocol: "UniswapV2", }, { name: "multicall_without_recognised_swaps", fixture: "multicall_uniswap.json", expectSwaps: 0, isMulticall: true, }, { name: "uniswap_v3_decrease_liquidity", fixture: "uniswapv3_decrease_liquidity.json", expectSwaps: 0, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { payload := loadPayload(t, tc.fixture) raw, err := hex.DecodeString(strings.TrimPrefix(payload.InputData, "0x")) if err != nil { t.Fatalf("failed to decode input data: %v", err) } if len(raw) < 4 { t.Fatalf("payload too short: %d bytes", len(raw)) } ctx := &MulticallContext{Protocol: payload.Protocol} var swaps []*SwapCall if tc.isMulticall { swaps, err = DecodeSwapCallsFromMulticall(raw[4:], ctx) if err != nil { t.Fatalf("multicall decode failed: %v", err) } } else { swaps = decodeDirect(raw, ctx) } if len(swaps) != tc.expectSwaps { selector := strings.ToLower(hex.EncodeToString(raw[:4])) if !tc.isMulticall { // attempt direct decode based on selector for diagnostics var diag *SwapCall switch selector { case "414bf389": diag = decodeExactInputSingle(raw[4:], ctx) case "db3e2198": diag = decodeExactInput(raw[4:], ctx) case "38ed1739": diag = decodeUniswapV2Swap(selector, raw[4:], ctx) } if diag != nil { t.Fatalf("expected %d swaps, got %d (selector=%s, diag=%s)", tc.expectSwaps, len(swaps), selector, diag.String()) } } t.Fatalf("expected %d swaps, got %d (selector=%s)", tc.expectSwaps, len(swaps), selector) } if tc.expectSwaps == 0 { return } swap := swaps[0] if !selectorsMatch(swap.Selector, tc.expectSelector) { t.Fatalf("expected selector %s, got %s", tc.expectSelector, swap.Selector) } if !strings.EqualFold(swap.TokenIn.Hex(), tc.tokenIn) { t.Fatalf("expected tokenIn %s, got %s", tc.tokenIn, swap.TokenIn.Hex()) } if !strings.EqualFold(swap.TokenOut.Hex(), tc.tokenOut) { t.Fatalf("expected tokenOut %s, got %s", tc.tokenOut, swap.TokenOut.Hex()) } if tc.verifyProtocol != "" && !strings.EqualFold(swap.Protocol, tc.verifyProtocol) { t.Fatalf("expected protocol %s, got %s", tc.verifyProtocol, swap.Protocol) } if selectorEquals(tc.expectSelector, "db3e2198") { if swap.AmountOut == nil || swap.AmountOut.Sign() == 0 { t.Fatalf("expected non-zero amountOut for exactOutputSingle") } } if selectorEquals(tc.expectSelector, "414bf389") || selectorEquals(tc.expectSelector, "38ed1739") { if swap.AmountIn == nil || swap.AmountIn.Sign() == 0 { t.Fatalf("expected non-zero amountIn for selector %s", tc.expectSelector) } } }) } } func normalizeSelectorHex(sel string) string { s := strings.TrimSpace(strings.ToLower(sel)) return strings.TrimPrefix(s, "0x") } func selectorEquals(a, b string) bool { return normalizeSelectorHex(a) == normalizeSelectorHex(b) } func selectorsMatch(actual, expected string) bool { return selectorEquals(actual, expected) }