package attesto import ( "context" "encoding/json" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "time" ) const ( anchorRoot = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" anchorTx = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" anchorBlock = int64(12345) ) func anchorEpochFixture() map[string]any { return map[string]any{ "anchorEpochId": "aep_demo", "merkleRoot": "0x" + anchorRoot, "txHash": anchorTx, "blockNumber": float64(anchorBlock), "anchorBatchId": "batch_demo", "chainId": float64(137), "payload": map[string]any{"contract_address": "0x" + strings.Repeat("d", 40)}, } } func mockRPC(t *testing.T, callRoot string, receipt map[string]any) *httptest.Server { t.Helper() return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var body struct { Method string `json:"method"` Params []any `json:"params"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { w.WriteHeader(http.StatusBadRequest) return } switch body.Method { case "eth_call": // (bytes32, uint32, string, uint64) — only the first word matters. result := "0x" + callRoot + strings.Repeat("0", 192) _ = json.NewEncoder(w).Encode(map[string]any{"jsonrpc": "2.0", "id": 1, "result": result}) case "eth_getTransactionReceipt": _ = json.NewEncoder(w).Encode(map[string]any{"jsonrpc": "2.0", "id": 1, "result": receipt}) default: w.WriteHeader(http.StatusBadRequest) } })) } func TestEncodeGetCommitmentCallMatchesPinnedExample(t *testing.T) { want := "0xa7b09e2a" + "0000000000000000000000000000000000000000000000000000000000000020" + "000000000000000000000000000000000000000000000000000000000000000a" + "62617463685f64656d6f00000000000000000000000000000000000000000000" if got := EncodeGetCommitmentCall("batch_demo"); got != want { t.Errorf("calldata = %s, want %s", got, want) } } func TestABIStillDeclaresGetCommitmentString(t *testing.T) { raw, err := os.ReadFile(filepath.Join("testdata", "APSProvenance.abi.json")) if err != nil { t.Fatal(err) } var abi []map[string]any if err := json.Unmarshal(raw, &abi); err != nil { t.Fatal(err) } for _, entry := range abi { if entry["name"] == "getCommitment" { inputs := entry["inputs"].([]any) if len(inputs) != 1 || inputs[0].(map[string]any)["type"] != "string" { t.Fatalf("getCommitment inputs changed: %v", inputs) } return } } t.Fatal("getCommitment not found in ABI") } func TestAnchorVerifiesWhenChainMatches(t *testing.T) { srv := mockRPC(t, anchorRoot, map[string]any{"status": "0x1", "blockNumber": "0x3039"}) defer srv.Close() report := VerifyAnchorOnchain(context.Background(), anchorEpochFixture(), srv.URL, time.Second) if !report.OK { t.Errorf("expected ok, problems: %v", report.Problems) } } func TestAnchorRootMismatch(t *testing.T) { srv := mockRPC(t, strings.Repeat("e", 64), map[string]any{"status": "0x1", "blockNumber": "0x3039"}) defer srv.Close() report := VerifyAnchorOnchain(context.Background(), anchorEpochFixture(), srv.URL, time.Second) if report.OK || !containsProblem(report.Problems, "anchor merkle root mismatch") { t.Errorf("problems: %v", report.Problems) } } func TestAnchorFailedTransaction(t *testing.T) { srv := mockRPC(t, anchorRoot, map[string]any{"status": "0x0", "blockNumber": "0x3039"}) defer srv.Close() report := VerifyAnchorOnchain(context.Background(), anchorEpochFixture(), srv.URL, time.Second) if !containsProblem(report.Problems, "anchor transaction failed") { t.Errorf("problems: %v", report.Problems) } } func TestAnchorWrongBlock(t *testing.T) { srv := mockRPC(t, anchorRoot, map[string]any{"status": "0x1", "blockNumber": "0x3040"}) defer srv.Close() report := VerifyAnchorOnchain(context.Background(), anchorEpochFixture(), srv.URL, time.Second) if !containsProblem(report.Problems, "anchor transaction block mismatch") { t.Errorf("problems: %v", report.Problems) } } func containsProblem(problems []string, want string) bool { for _, p := range problems { if p == want { return true } } return false }