Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 0 additions & 187 deletions benchmarks/perp-exit-custody.yml

This file was deleted.

212 changes: 0 additions & 212 deletions benchmarks/perp-fees-at-size.yml

This file was deleted.

198 changes: 0 additions & 198 deletions benchmarks/perp-fill-rate-1m.yml

This file was deleted.

127 changes: 127 additions & 0 deletions harnesses/perp-protocol-longevity/cmd/script/defillama.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package main

import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
)

type dlHack struct {
Date int64 `json:"date"`
Name string `json:"name"`
Classification string `json:"classification"`
Amount float64 `json:"amount"`
Source string `json:"source"`
ReturnedFunds *float64 `json:"returnedFunds"`
}

func fetchDefiLlamaHacks() ([]dlHack, error) {
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Get("https://api.llama.fi/hacks")
if err != nil {
return nil, fmt.Errorf("fetch: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != 200 {
return nil, fmt.Errorf("status_%d", resp.StatusCode)
}
var hacks []dlHack
if err := json.Unmarshal(body, &hacks); err != nil {
return nil, fmt.Errorf("parse: %w", err)
}
return hacks, nil
}

// enrichFromDefiLlama fetches the DeFiLlama hacks feed and appends newly-discovered
// incidents into the registry. Rules per the bench methodology:
// - Classification "Infrastructure" = front-end/phishing, skip
// - Incident date before venue launch = skip (filters pre-launch false positives)
// - Net-zero incidents (returnedFunds >= amount) = skip (no permanent loss)
// - Already-recorded incidents (±48h match) = skip
func enrichFromDefiLlama(reg []venueRecord) []venueRecord {
hacks, err := fetchDefiLlamaHacks()
if err != nil {
fmt.Printf("[LONGEVITY] DeFiLlama fetch failed: %v — using embedded registry\n", err)
return reg
}
fmt.Printf("[LONGEVITY] DeFiLlama: fetched %d incidents\n", len(hacks))

for i := range reg {
if len(reg[i].DefiLlamaNames) == 0 {
continue
}
for _, h := range hacks {
if !dlMatchesVenue(h, reg[i].DefiLlamaNames) {
continue
}
if h.Classification == "Infrastructure" {
continue
}
t := time.Unix(h.Date, 0).UTC()
if t.Before(reg[i].Launched) {
continue
}
if h.ReturnedFunds != nil && *h.ReturnedFunds >= h.Amount {
continue
}
if dlAlreadyRecorded(reg[i].Incidents, t) {
continue
}
net := h.Amount
if h.ReturnedFunds != nil {
net -= *h.ReturnedFunds
}
src := h.Source
if src == "" {
src = "https://defillama.com/hacks"
}
reg[i].Incidents = append(reg[i].Incidents, incidentRecord{
Date: t,
AmountUSD: net,
Kind: dlKind(h.Classification),
Source: src,
})
fmt.Printf("[LONGEVITY] DeFiLlama: +incident %s on %s ($%.0f net)\n",
reg[i].Slug, t.Format("2006-01-02"), net)
}
}
return reg
}

func dlMatchesVenue(h dlHack, names []string) bool {
lower := strings.ToLower(h.Name)
for _, n := range names {
if strings.Contains(lower, strings.ToLower(n)) {
return true
}
}
return false
}

func dlAlreadyRecorded(incidents []incidentRecord, t time.Time) bool {
for _, inc := range incidents {
diff := inc.Date.Sub(t)
if diff < 0 {
diff = -diff
}
if diff < 48*time.Hour {
return true
}
}
return false
}

func dlKind(classification string) string {
switch classification {
case "Oracle":
return "oracle-manipulation"
case "Access Control":
return "admin-key"
default:
return "exploit"
}
}
31 changes: 20 additions & 11 deletions harnesses/perp-protocol-longevity/cmd/script/main.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
// perp-protocol-longevity -- Bench 119
//
// Computes a live "days clean" counter for each perp DEX based on a
// versioned incident registry sourced from rekt.news and public post-mortems.
// No external API calls: all data is embedded in the binary at build time.
// versioned incident registry seeded from rekt.news / DeFiLlama post-mortems.
// The registry is enriched daily by polling https://api.llama.fi/hacks so new
// incidents are picked up without a harness redeploy.
//
// Metrics exposed on :2112/metrics:
// perp_protocol_days_clean{venue} -- primary, higher is better
// perp_protocol_incidents_total{venue}
// perp_protocol_incident_amount_usd{venue}
// perp_protocol_launch_timestamp_seconds{venue}
// perp_protocol_registry_last_updated_timestamp_seconds
// perp_protocol_health{venue}
//
// perp_protocol_days_clean{venue} -- primary, higher is better
// perp_protocol_incidents_total{venue}
// perp_protocol_incident_amount_usd{venue}
// perp_protocol_launch_timestamp_seconds{venue}
// perp_protocol_registry_last_updated_timestamp_seconds
// perp_protocol_health{venue}
package main

import (
Expand All @@ -23,7 +25,11 @@ import (

func main() {
fmt.Println("=== perp-protocol-longevity harness ===")
fmt.Println("Bench 119 -- days clean counter from embedded incident registry.")
fmt.Println("Bench 119 -- days clean counter, enriched daily from DeFiLlama.")

registry = enrichFromDefiLlama(registry)
lastEnriched := time.Now()

fmt.Printf("Registry date: %s. Venues: %d.\n", registryUpdatedAt.Format("2006-01-02"), len(registry))
fmt.Println("Exposes /metrics, /health on :2112.")

Expand All @@ -37,7 +43,6 @@ func main() {
}
}()

// Publish immediately, then refresh the day counter every hour.
publishAll()
logCurrentCounts()

Expand All @@ -50,6 +55,10 @@ func main() {
fmt.Println("shutting down")
return
case <-tick.C:
if time.Since(lastEnriched) >= 24*time.Hour {
registry = enrichFromDefiLlama(registry)
lastEnriched = time.Now()
}
publishAll()
logCurrentCounts()
}
Expand All @@ -65,7 +74,7 @@ func logCurrentCounts() {
fmt.Printf("[LONGEVITY][%s] %.1f days clean since launch (%s), 0 incidents\n",
vr.Slug, days, vr.Launched.Format("2006-01-02"))
} else {
fmt.Printf("[LONGEVITY][%s] %.1f days since last incident, %d total incident(s), $%.0f lost\n",
fmt.Printf("[LONGEVITY][%s] %.1f days since last incident, %d total incident(s), $%.0f net lost\n",
vr.Slug, days, len(vr.Incidents), totalIncidentAmount(vr))
}
}
Expand Down
4 changes: 2 additions & 2 deletions harnesses/perp-protocol-longevity/cmd/script/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ func init() {

healthGauge = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "perp_protocol_health",
Help: "Always 1 for this harness (no external calls, computed from embedded registry).",
Help: "Always 1 for this harness (computed from embedded registry, enriched daily from DeFiLlama).",
}, []string{"venue"})
prometheus.MustRegister(healthGauge)
}

// registryUpdatedAt is the date the embedded registry was last reviewed.
var registryUpdatedAt = time.Date(2026, 8, 2, 0, 0, 0, 0, time.UTC)
var registryUpdatedAt = time.Date(2026, 8, 3, 0, 0, 0, 0, time.UTC)

func publishAll() {
registryTimestampGauge.Set(float64(registryUpdatedAt.Unix()))
Expand Down
118 changes: 80 additions & 38 deletions harnesses/perp-protocol-longevity/cmd/script/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,82 +4,124 @@ import "time"

// incidentRecord is one confirmed security incident for a venue.
// Only events resulting in direct theft or permanent loss of user funds
// via a protocol vulnerability are included.
// via a protocol vulnerability are included. Net loss is stored (gross minus returned).
type incidentRecord struct {
Date time.Time
AmountUSD float64
Kind string // "exploit", "oracle-manipulation", "admin-key"
Source string // public post-mortem URL
Source string // public post-mortem or DeFiLlama URL
}

// venueRecord holds all known incidents for a venue plus its launch date.
// DefiLlamaNames: substring matches against DeFiLlama hack.Name (case-insensitive).
// Empty slice = no auto-enrichment (used when DeFiLlama would pull wrong-version incidents).
type venueRecord struct {
Slug string
Name string
Launched time.Time
Incidents []incidentRecord
Slug string
Name string
Launched time.Time
Incidents []incidentRecord
DefiLlamaNames []string
}

// Registry version: 2026-08-02. Updated from rekt.news and public post-mortems.
// A venue with no incidents uses its launch date as the clean-streak start.
// Incidents excluded per methodology: front-end phishing, market-structure
// events (liquidation cascades, forced close, governance disputes), oracle
// price manipulation without a smart contract vulnerability.
// Registry seeded from rekt.news / DeFiLlama as of 2026-08-03.
// Known incidents are pre-seeded so the registry works without network access.
// enrichFromDefiLlama() supplements this daily with newly-discovered incidents.
var registry = []venueRecord{
{
Slug: "gains",
Name: "gains.trade",
Launched: time.Date(2021, 12, 1, 0, 0, 0, 0, time.UTC),
// No incidents recorded in rekt.news or public post-mortems as of 2026-08-02.
// The synthetic architecture (oracle pricing, no custodial vault drainable
// without position netting, no privileged admin key on critical paths)
// reduces the attack surface relative to AMM-based vault protocols.
Incidents: []incidentRecord{},
// No incidents recorded in DeFiLlama or public post-mortems as of 2026-08-03.
Incidents: []incidentRecord{},
DefiLlamaNames: []string{"gains.trade", "gains network"},
},
{
Slug: "gmx",
Name: "GMX v2",
Launched: time.Date(2023, 8, 1, 0, 0, 0, 0, time.UTC),
Name: "GMX",
Launched: time.Date(2021, 9, 1, 0, 0, 0, 0, time.UTC),
Incidents: []incidentRecord{
// V1 oracle/price attack on Arbitrum, Sept 2022.
{
Date: time.Date(2022, 9, 18, 0, 0, 0, 0, time.UTC),
AmountUSD: 565_000,
Kind: "oracle-manipulation",
Source: "https://defillama.com/hacks",
},
// V1 re-entrancy exploit July 2025. $42M taken; $40M returned by attacker.
// Net permanent loss: $2M. Resets the clean-streak counter.
{
Date: time.Date(2025, 7, 9, 0, 0, 0, 0, time.UTC),
AmountUSD: 42_000_000,
AmountUSD: 2_000_000,
Kind: "exploit",
Source: "https://coinperps.xyz/gmx-v2-exploit-july-2025",
Source: "https://defillama.com/hacks",
},
},
DefiLlamaNames: []string{"gmx"},
},
{
Slug: "hyperliquid",
Name: "Hyperliquid",
Launched: time.Date(2023, 11, 1, 0, 0, 0, 0, time.UTC),
// No protocol exploit recorded. The March 2025 JellyJelly event was a
// market-structure incident (large-position liquidation cascade triggering
// a validator committee emergency close). No smart contract was compromised
// and no user funds were stolen via a protocol vulnerability. Excluded per
// the methodology's incident criteria.
Incidents: []incidentRecord{},
// market-structure incident (liquidation cascade + validator committee
// emergency close). No smart contract compromised, no funds stolen via a
// protocol vulnerability. Excluded per methodology.
// DeFiLlama has a pre-launch "Hyperliquid" entry (June 2023, $37K) that
// predates mainnet by 5 months — filtered by the Launched date check.
Incidents: []incidentRecord{},
DefiLlamaNames: []string{"hyperliquid"},
},
{
Slug: "dydx",
Name: "dYdX v4",
Launched: time.Date(2023, 10, 1, 0, 0, 0, 0, time.UTC),
// No exploit on the v4 Cosmos appchain deployment. dYdX v1/v2 on Ethereum
// had oracle manipulation events in 2020-2021 but those ran on a different
// contract and are out of scope for the v4 deployment tracked here.
Incidents: []incidentRecord{},
// The Nov 2023 $9M incident was on dYdX V3 (Ethereum). Out of scope for
// the V4 Cosmos appchain deployment tracked here. No V4 exploit recorded.
// DefiLlamaNames intentionally empty: DeFiLlama tracks dYdX as one entity
// and would pull V3 incidents into V4.
Incidents: []incidentRecord{},
DefiLlamaNames: []string{},
},
{
Slug: "lighter",
Name: "Lighter",
Launched: time.Date(2023, 7, 1, 0, 0, 0, 0, time.UTC),
Incidents: []incidentRecord{},
DefiLlamaNames: []string{"lighter"},
},
{
Slug: "paradex",
Name: "Paradex",
Launched: time.Date(2023, 10, 1, 0, 0, 0, 0, time.UTC),
Incidents: []incidentRecord{},
DefiLlamaNames: []string{"paradex"},
},
{
Slug: "aster",
Name: "Aster",
Launched: time.Date(2024, 12, 1, 0, 0, 0, 0, time.UTC),
// Aster DEX went live under this brand after the APX Finance merger in Dec 2024.
Incidents: []incidentRecord{},
DefiLlamaNames: []string{"asterdex", "aster dex"},
},
{
Slug: "lighter",
Name: "Lighter",
Launched: time.Date(2023, 7, 1, 0, 0, 0, 0, time.UTC),
Incidents: []incidentRecord{},
Slug: "edgex",
Name: "EdgeX",
Launched: time.Date(2024, 10, 3, 0, 0, 0, 0, time.UTC),
// StarkEx-powered non-custodial perp DEX, mainnet launch Oct 3, 2024.
Incidents: []incidentRecord{},
DefiLlamaNames: []string{"edgex"},
},
{
Slug: "paradex",
Name: "Paradex",
Launched: time.Date(2023, 10, 1, 0, 0, 0, 0, time.UTC),
Incidents: []incidentRecord{},
Slug: "polymarket",
Name: "Polymarket Perps",
Launched: time.Date(2026, 7, 8, 0, 0, 0, 0, time.UTC),
// Perps product launched July 8, 2026. Two DeFiLlama incidents for "Polymarket"
// (May-June 2026) are on the prediction market product, not perps, and predate
// the perps launch — filtered by the Launched date check.
// DefiLlamaNames intentionally empty to avoid false-positive prediction-market matches.
Incidents: []incidentRecord{},
DefiLlamaNames: []string{},
},
}

Expand All @@ -98,7 +140,7 @@ func cleanStreakStart(vr venueRecord) int64 {
return last.Unix()
}

// totalIncidentAmount returns the sum of USD losses across all recorded incidents.
// totalIncidentAmount returns the sum of net USD losses across all recorded incidents.
func totalIncidentAmount(vr venueRecord) float64 {
var total float64
for _, inc := range vr.Incidents {
Expand Down
13 changes: 13 additions & 0 deletions harnesses/perp-protocol-longevity/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,16 @@ module perp-protocol-longevity
go 1.24.0

require github.com/prometheus/client_golang v1.23.2

require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/sys v0.35.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
)
Loading
Loading