0.7.45
This commit is contained in:
@@ -75,3 +75,4 @@
|
|||||||
0.7.42 - Consolidation famille Raydium : audit conservatoire des instructions Raydium non décodées, décodage CLMM legacy `swap`, cleanup des audits remplacés, classification HTTP `getTransaction` comme requête lourde avec retry/backoff de backfill, mapping des événements non-swap prouvés `raydium_clmm` (`increase_liquidity_v2`, `decrease_liquidity_v2`, `open_position_with_token22_nft`, `close_position`) et `raydium_cpmm` (`initialize`, `withdraw`, `collect_creator_fee`), matérialisation de 25 liquidity events, 1 lifecycle event et 2 fee events sur corpus élargi, conservation des non-swaps AMM v4 legacy en audit.
|
0.7.42 - Consolidation famille Raydium : audit conservatoire des instructions Raydium non décodées, décodage CLMM legacy `swap`, cleanup des audits remplacés, classification HTTP `getTransaction` comme requête lourde avec retry/backoff de backfill, mapping des événements non-swap prouvés `raydium_clmm` (`increase_liquidity_v2`, `decrease_liquidity_v2`, `open_position_with_token22_nft`, `close_position`) et `raydium_cpmm` (`initialize`, `withdraw`, `collect_creator_fee`), matérialisation de 25 liquidity events, 1 lifecycle event et 2 fee events sur corpus élargi, conservation des non-swaps AMM v4 legacy en audit.
|
||||||
0.7.43-E5C - Reprise documentaire et normalisation DEX-first : `0.7.43` est conservé comme point de reprise non clos pour le lot Meteora, la suite est redécoupée par DEX/version séparés, le besoin d’un ledger de décodage/replay est acté, les statuts `known` / `observed` / `decoded` / `materialized` / `verified_by_corpus` deviennent obligatoires, et aucun `program_id` ne doit être marqué vérifié sans preuve/corpus reproductible.
|
0.7.43-E5C - Reprise documentaire et normalisation DEX-first : `0.7.43` est conservé comme point de reprise non clos pour le lot Meteora, la suite est redécoupée par DEX/version séparés, le besoin d’un ledger de décodage/replay est acté, les statuts `known` / `observed` / `decoded` / `materialized` / `verified_by_corpus` deviennent obligatoires, et aucun `program_id` ne doit être marqué vérifié sans preuve/corpus reproductible.
|
||||||
0.7.44 - Ledger de décodage/replay DEX : ajout de `k_sol_dex_decode_replay_ledger`, des DTO/entities/queries associés, des re-exports DB/lib, et intégration dans le replay local pour skipper uniquement l’étape de décodage DEX lorsqu’un passage certifié existe pour la même version logique de decoder. Les transactions multi-event ou multi-token restent marquées `unsafe` et sont redécodées sauf option future plus explicite ; le replay continue de reconstruire détection, matérialisation, trades, candles et classifications à partir des events persistés.
|
0.7.44 - Ledger de décodage/replay DEX : ajout de `k_sol_dex_decode_replay_ledger`, des DTO/entities/queries associés, des re-exports DB/lib, et intégration dans le replay local pour skipper uniquement l’étape de décodage DEX lorsqu’un passage certifié existe pour la même version logique de decoder. Les transactions multi-event ou multi-token restent marquées `unsafe` et sont redécodées sauf option future plus explicite ; le replay continue de reconstruire détection, matérialisation, trades, candles et classifications à partir des events persistés.
|
||||||
|
0.7.45 - Meteora DLMM normalisation finale : consolidation séparée de `meteora_dlmm` sur corpus dédié, maintien du wrapper Anchor `anchor_self_cpi_log` `e445a52e51cb9a1d`, enrichissement des swaps via `Swap` / `Swap2Evt`, cleanup des audits Anchor CPI swap déjà couverts, ajout des events Carbon/IDL observés et vérifiés par corpus (`lb_pair_create_event`, `add_liquidity_event`, `remove_liquidity_event`, `claim_fee_event`, `position_create_event`, `position_close_event`, `close_position_if_empty`, `remove_liquidity_by_range2`, `add_liquidity_by_strategy2`, `add_liquidity_by_weight`), conservation des deux audits résiduels `e8abf2613a4d232d` en `instruction_audit` faute de mapping Carbon/IDL confirmé, matérialisation locale validée avec `15` liquidity events et `6` lifecycle events sur le corpus DLMM élargi, et version logique replay `dex_decode.v0.7.45.dlmm_add_liquidity_strategies1`. Aucun nouveau `program_id` n’est déclaré vérifié sans preuve/corpus reproductible.
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.7.44"
|
version = "0.7.45"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot"
|
repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot"
|
||||||
|
|||||||
64
README.md
64
README.md
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
`khadhroony-bobobot` est un workspace Rust destiné à la détection, au décodage, à l’analyse et, à terme, au trading semi-automatisé de tokens Solana.
|
`khadhroony-bobobot` est un workspace Rust destiné à la détection, au décodage, à l’analyse et, à terme, au trading semi-automatisé de tokens Solana.
|
||||||
|
|
||||||
Ce document reflète le point de reprise `0.7.43-E5C`. La version Cargo reste `0.7.43`, mais le lot Meteora ouvert en `0.7.43` n’est pas considéré comme terminé. La priorité immédiate est maintenant de normaliser la roadmap DEX-first, d’ajouter un ledger de décodage/replay pour éviter les rescans inutiles, puis de reprendre les DEX un par un, variante par variante.
|
Ce document reflète le point de reprise `0.7.43-E5C` et l’état de consolidation atteint après `0.7.45` pour `meteora_dlmm`. La version Cargo a évolué ensuite à `0.7.45` côté `kb_lib`. Le lot Meteora initialement ouvert en bloc a été redécoupé : `meteora_dlmm` est traité séparément, puis la suite reprend `meteora_damm_v1`, `meteora_damm_v2` et `meteora_dbc` un par un.
|
||||||
|
|
||||||
## 1. Objectif
|
## 1. Objectif
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ Les surfaces suivantes existent dans le code, dans la matrice ou dans le corpus
|
|||||||
| `raydium_cpmm` | DEX effectif consolidé partiellement | Swaps et premiers events non-trade prouvés sur corpus antérieur. |
|
| `raydium_cpmm` | DEX effectif consolidé partiellement | Swaps et premiers events non-trade prouvés sur corpus antérieur. |
|
||||||
| `raydium_clmm` | DEX effectif consolidé partiellement | Swaps v2/legacy, positions et liquidity events prouvés sur corpus antérieur. |
|
| `raydium_clmm` | DEX effectif consolidé partiellement | Swaps v2/legacy, positions et liquidity events prouvés sur corpus antérieur. |
|
||||||
| `raydium_amm_v4` | DEX effectif legacy | Swaps AMM v4 legacy matérialisés ; non-swaps legacy conservés en audit tant que le corpus ne permet pas une promotion fiable. |
|
| `raydium_amm_v4` | DEX effectif legacy | Swaps AMM v4 legacy matérialisés ; non-swaps legacy conservés en audit tant que le corpus ne permet pas une promotion fiable. |
|
||||||
| `meteora_dlmm` | DEX effectif à reprendre séparément | Présent et observé ; events non-trade à normaliser. |
|
| `meteora_dlmm` | DEX effectif consolidé en `0.7.45` | Swaps, Anchor CPI swap events, liquidity, positions, fees et lifecycle principaux validés par corpus local ; deux Anchor CPI audits résiduels `e8abf2613a4d232d` restent volontairement non mappés. |
|
||||||
| `meteora_damm_v1` | DEX effectif à reprendre séparément | Swaps présents ; plusieurs events restent en audit ou non actionnables. |
|
| `meteora_damm_v1` | DEX effectif à reprendre séparément | Swaps présents ; plusieurs events restent en audit ou non actionnables. |
|
||||||
| `meteora_damm_v2` | DEX effectif à reprendre séparément | Swaps et create_pool observés ; nombreux audits à traiter. |
|
| `meteora_damm_v2` | DEX effectif à reprendre séparément | Swaps et create_pool observés ; nombreux audits à traiter. |
|
||||||
| `meteora_dbc` | launch/bonding + DEX effectif partiel à reprendre séparément | Gros volume d’audits ; séparer bonding/launch, swap effectif et migration. |
|
| `meteora_dbc` | launch/bonding + DEX effectif partiel à reprendre séparément | Gros volume d’audits ; séparer bonding/launch, swap effectif et migration. |
|
||||||
@@ -112,6 +112,35 @@ Aucun `program_id`, DEX ou event ne doit être documenté comme vérifié sans p
|
|||||||
| `materialized` | L’event alimente une table métier dédiée : trade, liquidity, lifecycle, fee, reward, admin, mint/burn, etc. |
|
| `materialized` | L’event alimente une table métier dédiée : trade, liquidity, lifecycle, fee, reward, admin, mint/burn, etc. |
|
||||||
| `verified_by_corpus` | Validé par requêtes SQL, signatures/corpus reproductibles et invariants de validation. |
|
| `verified_by_corpus` | Validé par requêtes SQL, signatures/corpus reproductibles et invariants de validation. |
|
||||||
|
|
||||||
|
### 3.6. État validé de `meteora_dlmm` en `0.7.45`
|
||||||
|
|
||||||
|
La tranche `0.7.45` clôt la normalisation séparée de `meteora_dlmm` sur le corpus DLMM élargi constitué via `Demo3`, backfill par signatures anciennes et backfill par pool address.
|
||||||
|
|
||||||
|
Éléments validés :
|
||||||
|
|
||||||
|
| Famille | Events DLMM couverts |
|
||||||
|
|---|---|
|
||||||
|
| Swaps | `swap`, `swap2`, `swap_exact_out` lorsque présents, avec enrichissement `anchorSwapEvent` pour `Swap` / `Swap2Evt`. |
|
||||||
|
| Création / lifecycle | `create_pool`, `lb_pair_create_event`, `initialize_bin_array`, `initialize_position`. |
|
||||||
|
| Positions | `position_create_event`, `position_close_event`, `close_position_if_empty`. |
|
||||||
|
| Liquidité | `add_liquidity_event`, `add_liquidity_by_strategy2`, `add_liquidity_by_weight`, `remove_liquidity_event`, `remove_liquidity`, `remove_liquidity_by_range2`. |
|
||||||
|
| Fees | `claim_fee_event`, `claim_fee2`. |
|
||||||
|
| Rewards | Décodeurs Anchor CPI présents pour `claim_reward_event` / `fund_reward_event`, mais non observés dans le corpus final `0.7.45`. |
|
||||||
|
|
||||||
|
Validation locale finale sur la base DLMM dédiée :
|
||||||
|
|
||||||
|
| Indicateur | Valeur observée |
|
||||||
|
|---|---:|
|
||||||
|
| transactions rejouées | `3027` |
|
||||||
|
| trades matérialisés | `530` |
|
||||||
|
| liquidity events matérialisés | `15` |
|
||||||
|
| lifecycle events matérialisés | `6` |
|
||||||
|
| candles upsert | `2120` |
|
||||||
|
| audits DLMM résiduels | `2` |
|
||||||
|
|
||||||
|
Les deux audits restants sont `e445a52e51cb9a1d + e8abf2613a4d232d`. Ils restent en `meteora_dlmm.instruction_audit`, car aucun mapping Carbon/IDL suffisamment fiable n’a été confirmé. Ils ne bloquent pas la clôture de `0.7.45`.
|
||||||
|
|
||||||
|
|
||||||
## 4. Matrice DEX : priorité révisée
|
## 4. Matrice DEX : priorité révisée
|
||||||
|
|
||||||
À partir du point de reprise `0.7.43-E5C`, la priorité est :
|
À partir du point de reprise `0.7.43-E5C`, la priorité est :
|
||||||
@@ -200,11 +229,11 @@ Le modèle actuel contient déjà notamment :
|
|||||||
- `k_sol_transaction_classifications` ;
|
- `k_sol_transaction_classifications` ;
|
||||||
- `k_sol_protocol_candidates`.
|
- `k_sol_protocol_candidates`.
|
||||||
|
|
||||||
### 5.2. Prochaine évolution DB : ledger de décodage/replay
|
### 5.2. Ledger de décodage/replay implémenté en `0.7.44`
|
||||||
|
|
||||||
Le replay actuel peut retraiter trop largement les transactions déjà connues. La prochaine tranche DB doit ajouter une structure de suivi permettant de ne pas rescanner les events certains, sauf option explicite.
|
Le replay local dispose maintenant de la table `k_sol_dex_decode_replay_ledger`. Elle permet de ne pas relancer l’étape `DexDecodeService` lorsqu’une transaction a déjà été décodée avec certitude pour la même version logique de decoder. Les étapes de détection, matérialisation non-trade, trades, candles et classifications restent rejouées afin de reconstruire les tables dérivées après reset.
|
||||||
|
|
||||||
Objectifs :
|
Objectifs maintenus :
|
||||||
|
|
||||||
- mémoriser qu’une transaction/instruction a déjà été traitée par un decoder donné ;
|
- mémoriser qu’une transaction/instruction a déjà été traitée par un decoder donné ;
|
||||||
- stocker le statut de décodage : certain, partiel, inconnu, erreur, non-actionnable, multi-token ambigu ;
|
- stocker le statut de décodage : certain, partiel, inconnu, erreur, non-actionnable, multi-token ambigu ;
|
||||||
@@ -214,26 +243,23 @@ Objectifs :
|
|||||||
- ne pas skipper automatiquement les transactions multi-token, multi-pool ou multi-event ambiguës ;
|
- ne pas skipper automatiquement les transactions multi-token, multi-pool ou multi-event ambiguës ;
|
||||||
- conserver les failed transactions comme traçables mais non actionnables.
|
- conserver les failed transactions comme traçables mais non actionnables.
|
||||||
|
|
||||||
Nom de table à décider dans la tranche DB, par exemple :
|
Table actuelle :
|
||||||
|
|
||||||
- `k_sol_decode_attempts` ; ou
|
- `k_sol_dex_decode_replay_ledger`.
|
||||||
- `k_sol_replay_decode_ledger`.
|
|
||||||
|
|
||||||
Champs conceptuels attendus :
|
Champs principaux :
|
||||||
|
|
||||||
| Champ conceptuel | Rôle |
|
| Champ conceptuel | Rôle |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `transaction_id` / `signature` | rattachement transactionnel stable |
|
| `transaction_id` / `signature` | rattachement transactionnel stable |
|
||||||
| `instruction_id` / `instruction_index` | granularité instruction si possible |
|
| `decoder_scope` | périmètre logique du decoder, actuellement `dex_decode.local_pipeline` |
|
||||||
| `program_id` | programme effectivement traité |
|
|
||||||
| `protocol_name` | protocole/DEx résolu |
|
|
||||||
| `decoder_code` | decoder logique utilisé |
|
|
||||||
| `decoder_version` | version logique du decoder |
|
| `decoder_version` | version logique du decoder |
|
||||||
| `decode_status` | certain, partial, unknown, failed, skipped, ambiguous |
|
| `decode_status` | `decoded` ou `no_events` dans la première implémentation |
|
||||||
| `certainty` | niveau de confiance machine |
|
| `certainty` | `sure` ou `unsafe` |
|
||||||
| `event_count` | nombre d’events produits |
|
| `event_count` | nombre total d’events persistés |
|
||||||
| `payload_hash` / `instruction_hash` | détection de changement d’entrée |
|
| `distinct_token_mint_count` | garde-fou multi-token |
|
||||||
| `reason_code` / `error_json` | diagnostic sans panic |
|
| `force_replay_required` | indique que le décodage doit être relancé |
|
||||||
|
| `status_reason` | diagnostic lisible sans panic |
|
||||||
| `created_at` / `updated_at` | audit local |
|
| `created_at` / `updated_at` | audit local |
|
||||||
|
|
||||||
## 6. Politique de replay
|
## 6. Politique de replay
|
||||||
@@ -274,7 +300,7 @@ La priorité immédiate après le point de reprise `0.7.43-E5C` est :
|
|||||||
|
|
||||||
1. `0.7.43` : resynchronisation documentaire, normalisation DEX-first et gel du point de reprise ;
|
1. `0.7.43` : resynchronisation documentaire, normalisation DEX-first et gel du point de reprise ;
|
||||||
2. `0.7.44` : ajout du ledger de décodage/replay et options de replay `force` / skip sûr ;
|
2. `0.7.44` : ajout du ledger de décodage/replay et options de replay `force` / skip sûr ;
|
||||||
3. `0.7.45` : reprise séparée de `meteora_dlmm` ;
|
3. `0.7.45` : reprise séparée de `meteora_dlmm` — clôturée côté corpus DLMM actuel ;
|
||||||
4. `0.7.46` : reprise séparée de `meteora_damm_v1` ;
|
4. `0.7.46` : reprise séparée de `meteora_damm_v1` ;
|
||||||
5. `0.7.47` : reprise séparée de `meteora_damm_v2` ;
|
5. `0.7.47` : reprise séparée de `meteora_damm_v2` ;
|
||||||
6. `0.7.48` : reprise séparée de `meteora_dbc` ;
|
6. `0.7.48` : reprise séparée de `meteora_dbc` ;
|
||||||
|
|||||||
65
ROADMAP.md
65
ROADMAP.md
@@ -1054,19 +1054,66 @@ Reste à faire plus tard :
|
|||||||
|
|
||||||
- descendre le ledger au niveau instruction/program lorsque nécessaire ;
|
- descendre le ledger au niveau instruction/program lorsque nécessaire ;
|
||||||
- ajouter un hash d’entrée transaction/instruction pour détecter les mutations de payload ;
|
- ajouter un hash d’entrée transaction/instruction pour détecter les mutations de payload ;
|
||||||
- exposer l’option `force_decode_replay` dans l’UI si besoin ;
|
- ajouter des filtres plus fins côté UI pour diagnostiquer les lignes ledger `unsafe` ;
|
||||||
- ajouter des diagnostics dédiés dans `local_pipeline_diagnostics`.
|
- ajouter des diagnostics dédiés dans `local_pipeline_diagnostics`.
|
||||||
|
|
||||||
### 6.077. Version `0.7.45` — `meteora_dlmm` séparé
|
### 6.077. Version `0.7.45` — `meteora_dlmm` séparé
|
||||||
Objectif : consolider `meteora_dlmm` comme DEX effectif séparé, avec corpus dédié et events utiles au trading.
|
Objectif : consolider `meteora_dlmm` comme DEX effectif séparé, avec corpus dédié et events utiles au trading, sans mélanger DAMM v1, DAMM v2 ou DBC.
|
||||||
|
|
||||||
À faire :
|
Statut : clos sur le corpus DLMM local élargi.
|
||||||
|
|
||||||
- vérifier le ou les `program_id` par corpus local, pas seulement par constante ;
|
Fait :
|
||||||
- consolider swaps exploitables, add/remove liquidity, positions, lifecycle et audits restants ;
|
|
||||||
- matérialiser uniquement les events prouvés dans les tables dédiées ;
|
- constitution d’un corpus dédié `meteora_dlmm` via `Demo3`, backfill manuel des signatures anciennes du pool `HTvjzsfX3yU6BUodCjZ5vZkUrAxMDTrBs3CJaq43ashR`, puis backfill par pool address ;
|
||||||
- conserver tout event incomplet en `instruction_audit` ou non-actionnable ;
|
- confirmation locale du programme DLMM observé `LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo` dans les transactions du corpus ;
|
||||||
- ajouter les compteurs diagnostics par event kind.
|
- traitement du wrapper Anchor `anchor_self_cpi_log` `e445a52e51cb9a1d` ;
|
||||||
|
- mapping prouvé localement et par IDL/Carbon des Anchor CPI swap events : `516ce3becdd00ac4` -> `Swap`, `2e7452d7941b544d` -> `Swap2Evt` ;
|
||||||
|
- enrichissement du payload `meteora_dlmm.swap` avec `anchorSwapEvent`, montants et fees CPI décodés ;
|
||||||
|
- cleanup conservatoire des audits Anchor CPI swap déjà couverts par un swap DLMM matérialisé ;
|
||||||
|
- ajout des events Anchor CPI non-swap DLMM observés : `lb_pair_create_event`, `add_liquidity_event`, `remove_liquidity_event`, `claim_fee_event`, `claim_reward_event` / `fund_reward_event` côté decoder, `position_create_event`, `position_close_event` ;
|
||||||
|
- promotion du discriminant direct `claim_fee2` vers `meteora_dlmm.claim_fee2` ;
|
||||||
|
- promotion de `close_position_if_empty` comme event de lifecycle/position close prouvé localement ;
|
||||||
|
- promotion de `remove_liquidity_by_range2`, `add_liquidity_by_strategy2` et `add_liquidity_by_weight` selon les layouts Carbon et le corpus local ;
|
||||||
|
- matérialisation validée des families non-trade dans les tables dédiées, notamment `k_sol_liquidity_events`, `k_sol_pool_lifecycle_events` et `k_sol_fee_events` ;
|
||||||
|
- maintien du ledger replay avec `effective_event_count`, afin que les `.instruction_audit` informatifs ne rendent pas inutilement les transactions `unsafe` ;
|
||||||
|
- version logique finale du replay pour la tranche : `dex_decode.v0.7.45.dlmm_add_liquidity_strategies1` ;
|
||||||
|
- maintien de la règle : aucun nouveau `program_id` n’est vérifié sans corpus.
|
||||||
|
|
||||||
|
Validation locale finale observée sur la base DLMM dédiée :
|
||||||
|
|
||||||
|
| Indicateur | Valeur |
|
||||||
|
|---|---:|
|
||||||
|
| transactions rejouées | `3027` |
|
||||||
|
| trades matérialisés | `530` |
|
||||||
|
| liquidity events matérialisés | `15` |
|
||||||
|
| lifecycle events matérialisés | `6` |
|
||||||
|
| candles upsert | `2120` |
|
||||||
|
| audits DLMM résiduels | `2` |
|
||||||
|
|
||||||
|
Events DLMM observés après replay :
|
||||||
|
|
||||||
|
- `meteora_dlmm.swap` ;
|
||||||
|
- `meteora_dlmm.create_pool` ;
|
||||||
|
- `meteora_dlmm.lb_pair_create_event` ;
|
||||||
|
- `meteora_dlmm.initialize_bin_array` ;
|
||||||
|
- `meteora_dlmm.initialize_position` ;
|
||||||
|
- `meteora_dlmm.position_create_event` ;
|
||||||
|
- `meteora_dlmm.position_close_event` ;
|
||||||
|
- `meteora_dlmm.close_position_if_empty` ;
|
||||||
|
- `meteora_dlmm.add_liquidity_event` ;
|
||||||
|
- `meteora_dlmm.add_liquidity_by_strategy2` ;
|
||||||
|
- `meteora_dlmm.add_liquidity_by_weight` ;
|
||||||
|
- `meteora_dlmm.remove_liquidity_event` ;
|
||||||
|
- `meteora_dlmm.remove_liquidity` ;
|
||||||
|
- `meteora_dlmm.remove_liquidity_by_range2` ;
|
||||||
|
- `meteora_dlmm.claim_fee_event` ;
|
||||||
|
- `meteora_dlmm.claim_fee2`.
|
||||||
|
|
||||||
|
Limite conservée :
|
||||||
|
|
||||||
|
- `e445a52e51cb9a1d + e8abf2613a4d232d` reste en `meteora_dlmm.instruction_audit` avec `proofStatus = observed_local_corpus_anchor_self_cpi_log`, faute de mapping Carbon/IDL confirmé. Ces deux audits ne sont pas promus et ne bloquent pas la clôture de `0.7.45`.
|
||||||
|
|
||||||
|
Décision : `0.7.45` est clos pour `meteora_dlmm`. La suite immédiate est `0.7.46` sur `meteora_damm_v1` uniquement.
|
||||||
|
|
||||||
### 6.078. Version `0.7.46` — `meteora_damm_v1` séparé
|
### 6.078. Version `0.7.46` — `meteora_damm_v1` séparé
|
||||||
Objectif : reprendre `meteora_damm_v1` sans le mélanger à DAMM v2, DBC ou DLMM.
|
Objectif : reprendre `meteora_damm_v1` sans le mélanger à DAMM v2, DBC ou DLMM.
|
||||||
@@ -1400,7 +1447,7 @@ Préconditions considérées acquises avant cette reprise :
|
|||||||
Ordre de travail recommandé pour la suite :
|
Ordre de travail recommandé pour la suite :
|
||||||
|
|
||||||
1. `0.7.44` : ledger de décodage/replay et skip sûr ;
|
1. `0.7.44` : ledger de décodage/replay et skip sûr ;
|
||||||
2. `0.7.45` : `meteora_dlmm` ;
|
2. `0.7.45` : `meteora_dlmm` — clos ;
|
||||||
3. `0.7.46` : `meteora_damm_v1` ;
|
3. `0.7.46` : `meteora_damm_v1` ;
|
||||||
4. `0.7.47` : `meteora_damm_v2` ;
|
4. `0.7.47` : `meteora_damm_v2` ;
|
||||||
5. `0.7.48` : `meteora_dbc` ;
|
5. `0.7.48` : `meteora_dbc` ;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "kb-demo-app",
|
"name": "kb-demo-app",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.7.44",
|
"version": "0.7.45",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "kb-demo-app",
|
"productName": "kb-demo-app",
|
||||||
"version": "0.7.44",
|
"version": "0.7.45",
|
||||||
"identifier": "com.sasedev.kb-demo-app",
|
"identifier": "com.sasedev.kb-demo-app",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "npm run dev",
|
"beforeDevCommand": "npm run dev",
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ pub use dtos::ChainSlotDto;
|
|||||||
pub use dtos::ChainTransactionDto;
|
pub use dtos::ChainTransactionDto;
|
||||||
pub use dtos::DbMetadataDto;
|
pub use dtos::DbMetadataDto;
|
||||||
pub use dtos::DbRuntimeEventDto;
|
pub use dtos::DbRuntimeEventDto;
|
||||||
pub use dtos::DexDecodedEventDto;
|
|
||||||
pub use dtos::DexDecodeReplayLedgerDto;
|
pub use dtos::DexDecodeReplayLedgerDto;
|
||||||
|
pub use dtos::DexDecodedEventDto;
|
||||||
pub use dtos::DexDto;
|
pub use dtos::DexDto;
|
||||||
pub use dtos::FeeEventDto;
|
pub use dtos::FeeEventDto;
|
||||||
pub use dtos::KnownHttpEndpointDto;
|
pub use dtos::KnownHttpEndpointDto;
|
||||||
@@ -88,8 +88,8 @@ pub use entities::ChainSlotEntity;
|
|||||||
pub use entities::ChainTransactionEntity;
|
pub use entities::ChainTransactionEntity;
|
||||||
pub use entities::DbMetadataEntity;
|
pub use entities::DbMetadataEntity;
|
||||||
pub use entities::DbRuntimeEventEntity;
|
pub use entities::DbRuntimeEventEntity;
|
||||||
pub use entities::DexDecodedEventEntity;
|
|
||||||
pub use entities::DexDecodeReplayLedgerEntity;
|
pub use entities::DexDecodeReplayLedgerEntity;
|
||||||
|
pub use entities::DexDecodedEventEntity;
|
||||||
pub use entities::DexEntity;
|
pub use entities::DexEntity;
|
||||||
pub use entities::FeeEventEntity;
|
pub use entities::FeeEventEntity;
|
||||||
pub use entities::KnownHttpEndpointEntity;
|
pub use entities::KnownHttpEndpointEntity;
|
||||||
@@ -142,14 +142,16 @@ pub use queries::query_db_metadatas_list;
|
|||||||
pub use queries::query_db_metadatas_upsert;
|
pub use queries::query_db_metadatas_upsert;
|
||||||
pub use queries::query_db_runtime_events_insert;
|
pub use queries::query_db_runtime_events_insert;
|
||||||
pub use queries::query_db_runtime_events_list_recent;
|
pub use queries::query_db_runtime_events_list_recent;
|
||||||
|
pub use queries::query_dex_decode_replay_ledger_get_by_signature;
|
||||||
|
pub use queries::query_dex_decode_replay_ledger_get_by_transaction;
|
||||||
|
pub use queries::query_dex_decode_replay_ledger_upsert;
|
||||||
pub use queries::query_dex_decoded_events_delete_by_key;
|
pub use queries::query_dex_decoded_events_delete_by_key;
|
||||||
|
pub use queries::query_dex_decoded_events_delete_meteora_dlmm_anchor_swap_instruction_audits;
|
||||||
|
pub use queries::query_dex_decoded_events_delete_related_instruction_audit;
|
||||||
pub use queries::query_dex_decoded_events_get_by_key;
|
pub use queries::query_dex_decoded_events_get_by_key;
|
||||||
pub use queries::query_dex_decoded_events_get_latest_pump_fun_create_payload_by_mint;
|
pub use queries::query_dex_decoded_events_get_latest_pump_fun_create_payload_by_mint;
|
||||||
pub use queries::query_dex_decoded_events_list_by_transaction_id;
|
pub use queries::query_dex_decoded_events_list_by_transaction_id;
|
||||||
pub use queries::query_dex_decoded_events_upsert;
|
pub use queries::query_dex_decoded_events_upsert;
|
||||||
pub use queries::query_dex_decode_replay_ledger_get_by_signature;
|
|
||||||
pub use queries::query_dex_decode_replay_ledger_get_by_transaction;
|
|
||||||
pub use queries::query_dex_decode_replay_ledger_upsert;
|
|
||||||
pub use queries::query_dexs_get_by_code;
|
pub use queries::query_dexs_get_by_code;
|
||||||
pub use queries::query_dexs_list;
|
pub use queries::query_dexs_list;
|
||||||
pub use queries::query_dexs_upsert;
|
pub use queries::query_dexs_upsert;
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ mod chain_transaction;
|
|||||||
mod db_metadata;
|
mod db_metadata;
|
||||||
mod db_runtime_event;
|
mod db_runtime_event;
|
||||||
mod dex;
|
mod dex;
|
||||||
mod dex_decoded_event;
|
|
||||||
mod dex_decode_replay_ledger;
|
mod dex_decode_replay_ledger;
|
||||||
|
mod dex_decoded_event;
|
||||||
mod fee_event;
|
mod fee_event;
|
||||||
mod known_http_endpoint;
|
mod known_http_endpoint;
|
||||||
mod known_ws_endpoint;
|
mod known_ws_endpoint;
|
||||||
@@ -66,14 +66,16 @@ pub use db_runtime_event::query_db_runtime_events_list_recent;
|
|||||||
pub use dex::query_dexs_get_by_code;
|
pub use dex::query_dexs_get_by_code;
|
||||||
pub use dex::query_dexs_list;
|
pub use dex::query_dexs_list;
|
||||||
pub use dex::query_dexs_upsert;
|
pub use dex::query_dexs_upsert;
|
||||||
|
pub use dex_decode_replay_ledger::query_dex_decode_replay_ledger_get_by_signature;
|
||||||
|
pub use dex_decode_replay_ledger::query_dex_decode_replay_ledger_get_by_transaction;
|
||||||
|
pub use dex_decode_replay_ledger::query_dex_decode_replay_ledger_upsert;
|
||||||
pub use dex_decoded_event::query_dex_decoded_events_delete_by_key;
|
pub use dex_decoded_event::query_dex_decoded_events_delete_by_key;
|
||||||
|
pub use dex_decoded_event::query_dex_decoded_events_delete_meteora_dlmm_anchor_swap_instruction_audits;
|
||||||
|
pub use dex_decoded_event::query_dex_decoded_events_delete_related_instruction_audit;
|
||||||
pub use dex_decoded_event::query_dex_decoded_events_get_by_key;
|
pub use dex_decoded_event::query_dex_decoded_events_get_by_key;
|
||||||
pub use dex_decoded_event::query_dex_decoded_events_get_latest_pump_fun_create_payload_by_mint;
|
pub use dex_decoded_event::query_dex_decoded_events_get_latest_pump_fun_create_payload_by_mint;
|
||||||
pub use dex_decoded_event::query_dex_decoded_events_list_by_transaction_id;
|
pub use dex_decoded_event::query_dex_decoded_events_list_by_transaction_id;
|
||||||
pub use dex_decoded_event::query_dex_decoded_events_upsert;
|
pub use dex_decoded_event::query_dex_decoded_events_upsert;
|
||||||
pub use dex_decode_replay_ledger::query_dex_decode_replay_ledger_get_by_signature;
|
|
||||||
pub use dex_decode_replay_ledger::query_dex_decode_replay_ledger_get_by_transaction;
|
|
||||||
pub use dex_decode_replay_ledger::query_dex_decode_replay_ledger_upsert;
|
|
||||||
pub use fee_event::query_fee_events_get_by_decoded_event_id;
|
pub use fee_event::query_fee_events_get_by_decoded_event_id;
|
||||||
pub use fee_event::query_fee_events_list_recent;
|
pub use fee_event::query_fee_events_list_recent;
|
||||||
pub use fee_event::query_fee_events_upsert;
|
pub use fee_event::query_fee_events_upsert;
|
||||||
|
|||||||
@@ -128,6 +128,109 @@ WHERE transaction_id = ?
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deletes decoded DEX instruction audit rows related to one decoded instruction.
|
||||||
|
///
|
||||||
|
/// This removes an audit row attached to the decoded instruction itself, its direct
|
||||||
|
/// parent instruction, or its direct child instructions inside the same transaction.
|
||||||
|
pub async fn query_dex_decoded_events_delete_related_instruction_audit(
|
||||||
|
database: &crate::Database,
|
||||||
|
transaction_id: i64,
|
||||||
|
instruction_id: i64,
|
||||||
|
audit_event_kind: &str,
|
||||||
|
) -> Result<u64, crate::Error> {
|
||||||
|
match database.connection() {
|
||||||
|
crate::DatabaseConnection::Sqlite(pool) => {
|
||||||
|
let query_result = sqlx::query(
|
||||||
|
r#"
|
||||||
|
DELETE FROM k_sol_dex_decoded_events
|
||||||
|
WHERE transaction_id = ?
|
||||||
|
AND event_kind = ?
|
||||||
|
AND instruction_id IN (
|
||||||
|
SELECT id
|
||||||
|
FROM k_sol_chain_instructions
|
||||||
|
WHERE transaction_id = ?
|
||||||
|
AND id = ?
|
||||||
|
|
||||||
|
UNION
|
||||||
|
|
||||||
|
SELECT parent_instruction_id
|
||||||
|
FROM k_sol_chain_instructions
|
||||||
|
WHERE transaction_id = ?
|
||||||
|
AND id = ?
|
||||||
|
AND parent_instruction_id IS NOT NULL
|
||||||
|
|
||||||
|
UNION
|
||||||
|
|
||||||
|
SELECT id
|
||||||
|
FROM k_sol_chain_instructions
|
||||||
|
WHERE transaction_id = ?
|
||||||
|
AND parent_instruction_id = ?
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(transaction_id)
|
||||||
|
.bind(audit_event_kind)
|
||||||
|
.bind(transaction_id)
|
||||||
|
.bind(instruction_id)
|
||||||
|
.bind(transaction_id)
|
||||||
|
.bind(instruction_id)
|
||||||
|
.bind(transaction_id)
|
||||||
|
.bind(instruction_id)
|
||||||
|
.execute(pool)
|
||||||
|
.await;
|
||||||
|
match query_result {
|
||||||
|
Ok(result) => return Ok(result.rows_affected()),
|
||||||
|
Err(error) => {
|
||||||
|
return Err(crate::Error::Db(format!(
|
||||||
|
"cannot delete related instruction audit events on sqlite: {}",
|
||||||
|
error
|
||||||
|
)));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes Meteora DLMM Anchor self-CPI swap audit rows already covered by decoded swaps.
|
||||||
|
///
|
||||||
|
/// This targets only local-corpus-observed Anchor event discriminators that are
|
||||||
|
/// decoded into `meteora_dlmm.swap` payload enrichment. It does not delete
|
||||||
|
/// unrelated DLMM instruction audits.
|
||||||
|
pub async fn query_dex_decoded_events_delete_meteora_dlmm_anchor_swap_instruction_audits(
|
||||||
|
database: &crate::Database,
|
||||||
|
transaction_id: i64,
|
||||||
|
) -> Result<u64, crate::Error> {
|
||||||
|
match database.connection() {
|
||||||
|
crate::DatabaseConnection::Sqlite(pool) => {
|
||||||
|
let query_result = sqlx::query(
|
||||||
|
r#"
|
||||||
|
DELETE FROM k_sol_dex_decoded_events
|
||||||
|
WHERE transaction_id = ?
|
||||||
|
AND protocol_name = 'meteora_dlmm'
|
||||||
|
AND event_kind = 'meteora_dlmm.instruction_audit'
|
||||||
|
AND json_extract(payload_json, '$.anchorSelfCpiLog') = 1
|
||||||
|
AND json_extract(payload_json, '$.anchorEventDiscriminatorHex') IN (
|
||||||
|
'516ce3becdd00ac4',
|
||||||
|
'2e7452d7941b544d'
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(transaction_id)
|
||||||
|
.execute(pool)
|
||||||
|
.await;
|
||||||
|
match query_result {
|
||||||
|
Ok(result) => return Ok(result.rows_affected()),
|
||||||
|
Err(error) => {
|
||||||
|
return Err(crate::Error::Db(format!(
|
||||||
|
"cannot delete Meteora DLMM Anchor swap audit events on sqlite: {}",
|
||||||
|
error
|
||||||
|
)));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Reads one decoded DEX event by its natural key.
|
/// Reads one decoded DEX event by its natural key.
|
||||||
pub async fn query_dex_decoded_events_get_by_key(
|
pub async fn query_dex_decoded_events_get_by_key(
|
||||||
database: &crate::Database,
|
database: &crate::Database,
|
||||||
@@ -400,4 +503,106 @@ mod tests {
|
|||||||
assert_eq!(listed.len(), 1);
|
assert_eq!(listed.len(), 1);
|
||||||
assert_eq!(listed[0].event_kind, "raydium_amm_v4.initialize2_pool");
|
assert_eq!(listed[0].event_kind, "raydium_amm_v4.initialize2_pool");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn related_instruction_audit_delete_removes_parent_audit_for_child_decode() {
|
||||||
|
let database = make_database().await;
|
||||||
|
let transaction_dto = crate::ChainTransactionDto::new(
|
||||||
|
"sig-dex-event-related-audit-test-1".to_string(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Some("helius_primary_http".to_string()),
|
||||||
|
Some("0".to_string()),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
r#"{"transaction":{"message":{"instructions":[]}}}"#.to_string(),
|
||||||
|
);
|
||||||
|
let transaction_id_result =
|
||||||
|
crate::query_chain_transactions_upsert(&database, &transaction_dto).await;
|
||||||
|
let transaction_id = match transaction_id_result {
|
||||||
|
Ok(transaction_id) => transaction_id,
|
||||||
|
Err(error) => panic!("transaction upsert must succeed: {}", error),
|
||||||
|
};
|
||||||
|
let parent_instruction_dto = crate::ChainInstructionDto::new(
|
||||||
|
transaction_id,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
Some(crate::METEORA_DLMM_PROGRAM_ID.to_string()),
|
||||||
|
Some("meteora-dlmm".to_string()),
|
||||||
|
Some(1),
|
||||||
|
r#"["ParentAccount","Pool111"]"#.to_string(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let parent_instruction_id_result =
|
||||||
|
crate::query_chain_instructions_insert(&database, &parent_instruction_dto).await;
|
||||||
|
let parent_instruction_id = match parent_instruction_id_result {
|
||||||
|
Ok(parent_instruction_id) => parent_instruction_id,
|
||||||
|
Err(error) => panic!("parent instruction insert must succeed: {}", error),
|
||||||
|
};
|
||||||
|
let child_instruction_dto = crate::ChainInstructionDto::new(
|
||||||
|
transaction_id,
|
||||||
|
Some(parent_instruction_id),
|
||||||
|
0,
|
||||||
|
Some(0),
|
||||||
|
Some(crate::METEORA_DLMM_PROGRAM_ID.to_string()),
|
||||||
|
Some("meteora-dlmm".to_string()),
|
||||||
|
Some(2),
|
||||||
|
r#"["ChildAccount","Pool111"]"#.to_string(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let child_instruction_id_result =
|
||||||
|
crate::query_chain_instructions_insert(&database, &child_instruction_dto).await;
|
||||||
|
let child_instruction_id = match child_instruction_id_result {
|
||||||
|
Ok(child_instruction_id) => child_instruction_id,
|
||||||
|
Err(error) => panic!("child instruction insert must succeed: {}", error),
|
||||||
|
};
|
||||||
|
let audit_dto = crate::DexDecodedEventDto::new(
|
||||||
|
transaction_id,
|
||||||
|
Some(parent_instruction_id),
|
||||||
|
"meteora_dlmm".to_string(),
|
||||||
|
crate::METEORA_DLMM_PROGRAM_ID.to_string(),
|
||||||
|
"meteora_dlmm.instruction_audit".to_string(),
|
||||||
|
Some("Pool111".to_string()),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
r#"{"audit":true}"#.to_string(),
|
||||||
|
);
|
||||||
|
let audit_upsert_result =
|
||||||
|
crate::query_dex_decoded_events_upsert(&database, &audit_dto).await;
|
||||||
|
match audit_upsert_result {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(error) => panic!("audit upsert must succeed: {}", error),
|
||||||
|
}
|
||||||
|
let deleted_result = super::query_dex_decoded_events_delete_related_instruction_audit(
|
||||||
|
&database,
|
||||||
|
transaction_id,
|
||||||
|
child_instruction_id,
|
||||||
|
"meteora_dlmm.instruction_audit",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let deleted = match deleted_result {
|
||||||
|
Ok(deleted) => deleted,
|
||||||
|
Err(error) => panic!("related audit delete must succeed: {}", error),
|
||||||
|
};
|
||||||
|
assert_eq!(deleted, 1);
|
||||||
|
let fetched_result = crate::query_dex_decoded_events_get_by_key(
|
||||||
|
&database,
|
||||||
|
transaction_id,
|
||||||
|
Some(parent_instruction_id),
|
||||||
|
"meteora_dlmm.instruction_audit",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let fetched = match fetched_result {
|
||||||
|
Ok(fetched) => fetched,
|
||||||
|
Err(error) => panic!("audit fetch must succeed: {}", error),
|
||||||
|
};
|
||||||
|
assert!(fetched.is_none());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,8 +38,10 @@ pub use meteora_dbc::MeteoraDbcSwapDecoded;
|
|||||||
pub use meteora_dlmm::MeteoraDlmmCreatePoolDecoded;
|
pub use meteora_dlmm::MeteoraDlmmCreatePoolDecoded;
|
||||||
pub use meteora_dlmm::MeteoraDlmmDecodedEvent;
|
pub use meteora_dlmm::MeteoraDlmmDecodedEvent;
|
||||||
pub use meteora_dlmm::MeteoraDlmmDecoder;
|
pub use meteora_dlmm::MeteoraDlmmDecoder;
|
||||||
|
pub use meteora_dlmm::MeteoraDlmmFeeDecoded;
|
||||||
pub use meteora_dlmm::MeteoraDlmmLiquidityDecoded;
|
pub use meteora_dlmm::MeteoraDlmmLiquidityDecoded;
|
||||||
pub use meteora_dlmm::MeteoraDlmmPoolLifecycleDecoded;
|
pub use meteora_dlmm::MeteoraDlmmPoolLifecycleDecoded;
|
||||||
|
pub use meteora_dlmm::MeteoraDlmmRewardDecoded;
|
||||||
pub use meteora_dlmm::MeteoraDlmmSwapDecoded;
|
pub use meteora_dlmm::MeteoraDlmmSwapDecoded;
|
||||||
pub use orca_whirlpools::OrcaWhirlpoolsCreatePoolDecoded;
|
pub use orca_whirlpools::OrcaWhirlpoolsCreatePoolDecoded;
|
||||||
pub use orca_whirlpools::OrcaWhirlpoolsDecodedEvent;
|
pub use orca_whirlpools::OrcaWhirlpoolsDecodedEvent;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
//! Persistence-oriented DEX decoding service.
|
//! Persistence-oriented DEX decoding service.
|
||||||
|
|
||||||
|
const METEORA_ANCHOR_SELF_CPI_LOG_SELECTOR_HEX: &str = "e445a52e51cb9a1d";
|
||||||
|
|
||||||
/// DEX decode service.
|
/// DEX decode service.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DexDecodeService {
|
pub struct DexDecodeService {
|
||||||
@@ -206,7 +208,7 @@ impl DexDecodeService {
|
|||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
};
|
};
|
||||||
let cleanup_result = self
|
let cleanup_result = self
|
||||||
.delete_replaced_raydium_instruction_audit(
|
.delete_replaced_instruction_audit(
|
||||||
transaction_id,
|
transaction_id,
|
||||||
instruction_id,
|
instruction_id,
|
||||||
protocol_name,
|
protocol_name,
|
||||||
@@ -219,7 +221,7 @@ impl DexDecodeService {
|
|||||||
return Ok(materialized);
|
return Ok(materialized);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_replaced_raydium_instruction_audit(
|
async fn delete_replaced_instruction_audit(
|
||||||
&self,
|
&self,
|
||||||
transaction_id: i64,
|
transaction_id: i64,
|
||||||
instruction_id: i64,
|
instruction_id: i64,
|
||||||
@@ -229,15 +231,14 @@ impl DexDecodeService {
|
|||||||
if event_kind.ends_with(".instruction_audit") {
|
if event_kind.ends_with(".instruction_audit") {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let audit_event_kind = match raydium_instruction_audit_event_kind_by_protocol(protocol_name)
|
let audit_event_kind = match instruction_audit_event_kind_by_protocol(protocol_name) {
|
||||||
{
|
|
||||||
Some(audit_event_kind) => audit_event_kind,
|
Some(audit_event_kind) => audit_event_kind,
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
};
|
};
|
||||||
let delete_result = crate::query_dex_decoded_events_delete_by_key(
|
let delete_result = crate::query_dex_decoded_events_delete_related_instruction_audit(
|
||||||
self.database.as_ref(),
|
self.database.as_ref(),
|
||||||
transaction_id,
|
transaction_id,
|
||||||
Some(instruction_id),
|
instruction_id,
|
||||||
audit_event_kind,
|
audit_event_kind,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
@@ -505,6 +506,42 @@ impl DexDecodeService {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
},
|
},
|
||||||
|
crate::MeteoraDlmmDecodedEvent::Fee(event) => {
|
||||||
|
return self
|
||||||
|
.materialize_named_dex_event(
|
||||||
|
transaction,
|
||||||
|
event.transaction_id,
|
||||||
|
event.instruction_id,
|
||||||
|
"meteora_dlmm",
|
||||||
|
event.program_id.clone(),
|
||||||
|
event.event_kind.as_str(),
|
||||||
|
event.pool_account.clone(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
event.payload_json.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
},
|
||||||
|
crate::MeteoraDlmmDecodedEvent::Reward(event) => {
|
||||||
|
return self
|
||||||
|
.materialize_named_dex_event(
|
||||||
|
transaction,
|
||||||
|
event.transaction_id,
|
||||||
|
event.instruction_id,
|
||||||
|
"meteora_dlmm",
|
||||||
|
event.program_id.clone(),
|
||||||
|
event.event_kind.as_str(),
|
||||||
|
event.pool_account.clone(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
event.payload_json.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1167,6 +1204,13 @@ impl DexDecodeService {
|
|||||||
if decoded_instruction_ids.contains(&instruction_id) {
|
if decoded_instruction_ids.contains(&instruction_id) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if is_meteora_dlmm_anchor_swap_log_replaced_by_decoded_swap(
|
||||||
|
audit_spec.protocol_name,
|
||||||
|
instruction,
|
||||||
|
decoded_events.as_slice(),
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let accounts = parse_instruction_accounts_vec(instruction.accounts_json.as_str());
|
let accounts = parse_instruction_accounts_vec(instruction.accounts_json.as_str());
|
||||||
let payload = build_meteora_instruction_audit_payload(
|
let payload = build_meteora_instruction_audit_payload(
|
||||||
transaction,
|
transaction,
|
||||||
@@ -1773,6 +1817,35 @@ fn candidate_meteora_audit_pool_account(
|
|||||||
return accounts.get(index).cloned();
|
return accounts.get(index).cloned();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_meteora_dlmm_anchor_swap_log_replaced_by_decoded_swap(
|
||||||
|
protocol_name: &str,
|
||||||
|
instruction: &crate::ChainInstructionDto,
|
||||||
|
decoded_events: &[crate::DexDecodedEventDto],
|
||||||
|
) -> bool {
|
||||||
|
if protocol_name != "meteora_dlmm" {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let data_base58 = parse_instruction_data_base58(instruction.data_json.as_deref());
|
||||||
|
let data_bytes = instruction_data_bytes_from_base58(data_base58.as_deref());
|
||||||
|
let selector_hex = discriminator_hex_from_bytes(data_bytes.as_deref(), 0);
|
||||||
|
if selector_hex.as_deref() != Some(METEORA_ANCHOR_SELF_CPI_LOG_SELECTOR_HEX) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let event_discriminator_hex = discriminator_hex_from_bytes(data_bytes.as_deref(), 8);
|
||||||
|
match event_discriminator_hex.as_deref() {
|
||||||
|
Some("516ce3becdd00ac4") | Some("2e7452d7941b544d") => {},
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
for decoded_event in decoded_events {
|
||||||
|
if decoded_event.protocol_name == "meteora_dlmm"
|
||||||
|
&& decoded_event.event_kind == "meteora_dlmm.swap"
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
fn build_meteora_instruction_audit_payload(
|
fn build_meteora_instruction_audit_payload(
|
||||||
transaction: &crate::ChainTransactionDto,
|
transaction: &crate::ChainTransactionDto,
|
||||||
instruction: &crate::ChainInstructionDto,
|
instruction: &crate::ChainInstructionDto,
|
||||||
@@ -1786,10 +1859,36 @@ fn build_meteora_instruction_audit_payload(
|
|||||||
None => 0,
|
None => 0,
|
||||||
};
|
};
|
||||||
let data_base58 = parse_instruction_data_base58(instruction.data_json.as_deref());
|
let data_base58 = parse_instruction_data_base58(instruction.data_json.as_deref());
|
||||||
let discriminator_hex = discriminator_hex_from_base58(data_base58.as_deref());
|
let data_bytes = instruction_data_bytes_from_base58(data_base58.as_deref());
|
||||||
|
let discriminator_hex = discriminator_hex_from_bytes(data_bytes.as_deref(), 0);
|
||||||
|
let anchor_self_cpi_log =
|
||||||
|
discriminator_hex.as_deref() == Some(METEORA_ANCHOR_SELF_CPI_LOG_SELECTOR_HEX);
|
||||||
|
let anchor_event_discriminator_hex = if anchor_self_cpi_log {
|
||||||
|
discriminator_hex_from_bytes(data_bytes.as_deref(), 8)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let anchor_event_payload_size = if anchor_self_cpi_log {
|
||||||
|
match data_bytes.as_ref() {
|
||||||
|
Some(data_bytes) => data_bytes.len().checked_sub(8),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
let data_prefix = data_base58
|
let data_prefix = data_base58
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|value| return value.chars().take(16).collect::<std::string::String>());
|
.map(|value| return value.chars().take(16).collect::<std::string::String>());
|
||||||
|
let audit_reason = if anchor_self_cpi_log {
|
||||||
|
"meteora_anchor_self_cpi_log_not_decoded_by_specific_event_decoder"
|
||||||
|
} else {
|
||||||
|
"meteora_instruction_not_decoded_by_specific_decoder"
|
||||||
|
};
|
||||||
|
let proof_status = if anchor_self_cpi_log {
|
||||||
|
"observed_local_corpus_anchor_self_cpi_log"
|
||||||
|
} else {
|
||||||
|
"unclassified_local_corpus_instruction"
|
||||||
|
};
|
||||||
return serde_json::json!({
|
return serde_json::json!({
|
||||||
"decoder": protocol_name,
|
"decoder": protocol_name,
|
||||||
"eventKind": event_kind,
|
"eventKind": event_kind,
|
||||||
@@ -1806,8 +1905,12 @@ fn build_meteora_instruction_audit_payload(
|
|||||||
"data": data_base58,
|
"data": data_base58,
|
||||||
"dataPrefix": data_prefix,
|
"dataPrefix": data_prefix,
|
||||||
"discriminatorHex": discriminator_hex,
|
"discriminatorHex": discriminator_hex,
|
||||||
"auditReason": "meteora_instruction_not_decoded_by_specific_decoder",
|
"anchorSelfCpiLog": anchor_self_cpi_log,
|
||||||
"proofStatus": "unclassified_local_corpus_instruction",
|
"anchorSelfCpiLogSelectorHex": if anchor_self_cpi_log { Some(METEORA_ANCHOR_SELF_CPI_LOG_SELECTOR_HEX) } else { None },
|
||||||
|
"anchorEventDiscriminatorHex": anchor_event_discriminator_hex,
|
||||||
|
"anchorEventPayloadSize": anchor_event_payload_size,
|
||||||
|
"auditReason": audit_reason,
|
||||||
|
"proofStatus": proof_status,
|
||||||
"tradeCandidate": false,
|
"tradeCandidate": false,
|
||||||
"candleCandidate": false,
|
"candleCandidate": false,
|
||||||
"nonTradeUseful": false,
|
"nonTradeUseful": false,
|
||||||
@@ -1816,13 +1919,14 @@ fn build_meteora_instruction_audit_payload(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn raydium_instruction_audit_event_kind_by_protocol(
|
fn instruction_audit_event_kind_by_protocol(
|
||||||
protocol_name: &str,
|
protocol_name: &str,
|
||||||
) -> std::option::Option<&'static str> {
|
) -> std::option::Option<&'static str> {
|
||||||
match protocol_name {
|
match protocol_name {
|
||||||
"raydium_amm_v4" => return Some("raydium_amm_v4.instruction_audit"),
|
"raydium_amm_v4" => return Some("raydium_amm_v4.instruction_audit"),
|
||||||
"raydium_clmm" => return Some("raydium_clmm.instruction_audit"),
|
"raydium_clmm" => return Some("raydium_clmm.instruction_audit"),
|
||||||
"raydium_cpmm" => return Some("raydium_cpmm.instruction_audit"),
|
"raydium_cpmm" => return Some("raydium_cpmm.instruction_audit"),
|
||||||
|
"meteora_dlmm" => return Some("meteora_dlmm.instruction_audit"),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1932,21 +2036,28 @@ fn parse_instruction_data_base58(
|
|||||||
fn discriminator_hex_from_base58(
|
fn discriminator_hex_from_base58(
|
||||||
data_base58: std::option::Option<&str>,
|
data_base58: std::option::Option<&str>,
|
||||||
) -> std::option::Option<std::string::String> {
|
) -> std::option::Option<std::string::String> {
|
||||||
let data_base58 = match data_base58 {
|
let bytes = instruction_data_bytes_from_base58(data_base58);
|
||||||
Some(data_base58) => data_base58,
|
return discriminator_hex_from_bytes(bytes.as_deref(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn discriminator_hex_from_bytes(
|
||||||
|
bytes: std::option::Option<&[u8]>,
|
||||||
|
offset: usize,
|
||||||
|
) -> std::option::Option<std::string::String> {
|
||||||
|
let bytes = match bytes {
|
||||||
|
Some(bytes) => bytes,
|
||||||
None => return None,
|
None => return None,
|
||||||
};
|
};
|
||||||
let bytes_result = bs58::decode(data_base58).into_vec();
|
if bytes.len() < offset + 8 {
|
||||||
let bytes = match bytes_result {
|
|
||||||
Ok(bytes) => bytes,
|
|
||||||
Err(_) => return None,
|
|
||||||
};
|
|
||||||
if bytes.len() < 8 {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let mut text = std::string::String::new();
|
let mut text = std::string::String::new();
|
||||||
for byte in bytes.iter().take(8) {
|
let mut index = offset;
|
||||||
|
let end = offset + 8;
|
||||||
|
while index < end {
|
||||||
|
let byte = bytes[index];
|
||||||
text.push_str(format!("{byte:02x}").as_str());
|
text.push_str(format!("{byte:02x}").as_str());
|
||||||
|
index += 1;
|
||||||
}
|
}
|
||||||
return Some(text);
|
return Some(text);
|
||||||
}
|
}
|
||||||
@@ -3012,4 +3123,17 @@ mod tests {
|
|||||||
};
|
};
|
||||||
assert_eq!(initialize.event_kind, "raydium_cpmm.initialize");
|
assert_eq!(initialize.event_kind, "raydium_cpmm.initialize");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn maps_instruction_audit_event_kind_for_raydium_and_meteora_dlmm_protocols() {
|
||||||
|
assert_eq!(
|
||||||
|
super::instruction_audit_event_kind_by_protocol("raydium_clmm"),
|
||||||
|
Some("raydium_clmm.instruction_audit")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
super::instruction_audit_event_kind_by_protocol("meteora_dlmm"),
|
||||||
|
Some("meteora_dlmm.instruction_audit")
|
||||||
|
);
|
||||||
|
assert_eq!(super::instruction_audit_event_kind_by_protocol("unknown"), None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -335,6 +335,9 @@ pub fn is_dex_liquidity_event_kind(event_kind: &str) -> bool {
|
|||||||
if event_kind.contains(".close_position") {
|
if event_kind.contains(".close_position") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if event_kind.contains(".position_close") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,6 +377,9 @@ pub fn is_dex_position_open_event_kind(event_kind: &str) -> bool {
|
|||||||
if event_kind.contains(".open_position") {
|
if event_kind.contains(".open_position") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if event_kind.contains(".position_create") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,6 +405,9 @@ pub fn is_dex_fee_event_kind(event_kind: &str) -> bool {
|
|||||||
if event_kind.contains("collect_fee") {
|
if event_kind.contains("collect_fee") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if event_kind.contains("claim_fee") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1041,6 +1050,19 @@ mod tests {
|
|||||||
assert!(!super::decoded_payload_has_trade_amount_or_price_payload(&empty_payload_json));
|
assert!(!super::decoded_payload_has_trade_amount_or_price_payload(&empty_payload_json));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn classifies_dlmm_claim_fee2_as_fee_collection() {
|
||||||
|
assert_eq!(super::classify_dex_event_category_code("meteora_dlmm.claim_fee2"), "fee");
|
||||||
|
assert_eq!(
|
||||||
|
super::classify_dex_event_lifecycle_kind_code("meteora_dlmm.claim_fee2"),
|
||||||
|
"fee_collection"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
super::classify_dex_event_actionability_code("meteora_dlmm.claim_fee2", false, false,),
|
||||||
|
"non_trade_useful"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn classifies_dlmm_add_remove_liquidity_and_positions_as_non_trade_useful() {
|
fn classifies_dlmm_add_remove_liquidity_and_positions_as_non_trade_useful() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -343,14 +343,14 @@ pub use db::DbRuntimeEventDto;
|
|||||||
pub use db::DbRuntimeEventEntity;
|
pub use db::DbRuntimeEventEntity;
|
||||||
/// Runtime event level used by the local database layer.
|
/// Runtime event level used by the local database layer.
|
||||||
pub use db::DbRuntimeEventLevel;
|
pub use db::DbRuntimeEventLevel;
|
||||||
/// Application-facing decoded DEX event DTO.
|
|
||||||
pub use db::DexDecodedEventDto;
|
|
||||||
/// Application-facing DEX decode replay ledger DTO.
|
/// Application-facing DEX decode replay ledger DTO.
|
||||||
pub use db::DexDecodeReplayLedgerDto;
|
pub use db::DexDecodeReplayLedgerDto;
|
||||||
/// Persisted decoded DEX event row.
|
|
||||||
pub use db::DexDecodedEventEntity;
|
|
||||||
/// Persisted DEX decode replay ledger row.
|
/// Persisted DEX decode replay ledger row.
|
||||||
pub use db::DexDecodeReplayLedgerEntity;
|
pub use db::DexDecodeReplayLedgerEntity;
|
||||||
|
/// Application-facing decoded DEX event DTO.
|
||||||
|
pub use db::DexDecodedEventDto;
|
||||||
|
/// Persisted decoded DEX event row.
|
||||||
|
pub use db::DexDecodedEventEntity;
|
||||||
/// Application-facing normalized DEX DTO.
|
/// Application-facing normalized DEX DTO.
|
||||||
pub use db::DexDto;
|
pub use db::DexDto;
|
||||||
/// Persisted normalized DEX row.
|
/// Persisted normalized DEX row.
|
||||||
@@ -591,8 +591,18 @@ pub use db::query_db_metadatas_upsert;
|
|||||||
pub use db::query_db_runtime_events_insert;
|
pub use db::query_db_runtime_events_insert;
|
||||||
/// Lists recent runtime events ordered from newest to oldest.
|
/// Lists recent runtime events ordered from newest to oldest.
|
||||||
pub use db::query_db_runtime_events_list_recent;
|
pub use db::query_db_runtime_events_list_recent;
|
||||||
|
/// Reads one DEX decode replay ledger row by signature and decoder identity.
|
||||||
|
pub use db::query_dex_decode_replay_ledger_get_by_signature;
|
||||||
|
/// Reads one DEX decode replay ledger row by transaction and decoder identity.
|
||||||
|
pub use db::query_dex_decode_replay_ledger_get_by_transaction;
|
||||||
|
/// Inserts or updates one DEX decode replay ledger row.
|
||||||
|
pub use db::query_dex_decode_replay_ledger_upsert;
|
||||||
/// Deletes one decoded DEX event row by its natural key.
|
/// Deletes one decoded DEX event row by its natural key.
|
||||||
pub use db::query_dex_decoded_events_delete_by_key;
|
pub use db::query_dex_decoded_events_delete_by_key;
|
||||||
|
/// Deletes Meteora DLMM Anchor self-CPI swap audit rows already covered by decoded swaps.
|
||||||
|
pub use db::query_dex_decoded_events_delete_meteora_dlmm_anchor_swap_instruction_audits;
|
||||||
|
/// Deletes decoded DEX instruction audit rows related to one decoded instruction.
|
||||||
|
pub use db::query_dex_decoded_events_delete_related_instruction_audit;
|
||||||
/// Reads one decoded DEX event by its natural key.
|
/// Reads one decoded DEX event by its natural key.
|
||||||
pub use db::query_dex_decoded_events_get_by_key;
|
pub use db::query_dex_decoded_events_get_by_key;
|
||||||
/// Returns the latest Pump.fun create payload associated with a token mint.
|
/// Returns the latest Pump.fun create payload associated with a token mint.
|
||||||
@@ -601,12 +611,6 @@ pub use db::query_dex_decoded_events_get_latest_pump_fun_create_payload_by_mint;
|
|||||||
pub use db::query_dex_decoded_events_list_by_transaction_id;
|
pub use db::query_dex_decoded_events_list_by_transaction_id;
|
||||||
/// Inserts or updates one decoded DEX event row.
|
/// Inserts or updates one decoded DEX event row.
|
||||||
pub use db::query_dex_decoded_events_upsert;
|
pub use db::query_dex_decoded_events_upsert;
|
||||||
/// Reads one DEX decode replay ledger row by signature and decoder identity.
|
|
||||||
pub use db::query_dex_decode_replay_ledger_get_by_signature;
|
|
||||||
/// Reads one DEX decode replay ledger row by transaction and decoder identity.
|
|
||||||
pub use db::query_dex_decode_replay_ledger_get_by_transaction;
|
|
||||||
/// Inserts or updates one DEX decode replay ledger row.
|
|
||||||
pub use db::query_dex_decode_replay_ledger_upsert;
|
|
||||||
/// Reads one normalized DEX row by code.
|
/// Reads one normalized DEX row by code.
|
||||||
pub use db::query_dexs_get_by_code;
|
pub use db::query_dexs_get_by_code;
|
||||||
/// Lists normalized DEX rows.
|
/// Lists normalized DEX rows.
|
||||||
@@ -921,10 +925,14 @@ pub use dex::MeteoraDlmmCreatePoolDecoded;
|
|||||||
pub use dex::MeteoraDlmmDecodedEvent;
|
pub use dex::MeteoraDlmmDecodedEvent;
|
||||||
/// Meteora DLMM decoder.
|
/// Meteora DLMM decoder.
|
||||||
pub use dex::MeteoraDlmmDecoder;
|
pub use dex::MeteoraDlmmDecoder;
|
||||||
|
/// Decoded Meteora DLMM fee collection event.
|
||||||
|
pub use dex::MeteoraDlmmFeeDecoded;
|
||||||
/// Decoded Meteora DLMM liquidity lifecycle event.
|
/// Decoded Meteora DLMM liquidity lifecycle event.
|
||||||
pub use dex::MeteoraDlmmLiquidityDecoded;
|
pub use dex::MeteoraDlmmLiquidityDecoded;
|
||||||
/// Decoded Meteora DLMM pool lifecycle event.
|
/// Decoded Meteora DLMM pool lifecycle event.
|
||||||
pub use dex::MeteoraDlmmPoolLifecycleDecoded;
|
pub use dex::MeteoraDlmmPoolLifecycleDecoded;
|
||||||
|
/// Decoded Meteora DLMM reward or emission event.
|
||||||
|
pub use dex::MeteoraDlmmRewardDecoded;
|
||||||
/// Decoded Meteora DLMM swap event.
|
/// Decoded Meteora DLMM swap event.
|
||||||
pub use dex::MeteoraDlmmSwapDecoded;
|
pub use dex::MeteoraDlmmSwapDecoded;
|
||||||
/// Decoded Orca Whirlpools create-pool event.
|
/// Decoded Orca Whirlpools create-pool event.
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
//! deterministic local pipeline over their signatures.
|
//! deterministic local pipeline over their signatures.
|
||||||
|
|
||||||
const LOCAL_PIPELINE_DEX_DECODER_SCOPE: &str = "dex_decode.local_pipeline";
|
const LOCAL_PIPELINE_DEX_DECODER_SCOPE: &str = "dex_decode.local_pipeline";
|
||||||
const LOCAL_PIPELINE_DEX_DECODER_VERSION: &str = "dex_decode.v0.7.44.ledger1";
|
const LOCAL_PIPELINE_DEX_DECODER_VERSION: &str =
|
||||||
|
"dex_decode.v0.7.45.dlmm_add_liquidity_strategies1";
|
||||||
|
|
||||||
fn default_skip_certified_dex_decode() -> bool {
|
fn default_skip_certified_dex_decode() -> bool {
|
||||||
return true;
|
return true;
|
||||||
@@ -193,8 +194,10 @@ impl LocalPipelineReplayService {
|
|||||||
signature = %signature,
|
signature = %signature,
|
||||||
"replaying local pipeline for persisted transaction"
|
"replaying local pipeline for persisted transaction"
|
||||||
);
|
);
|
||||||
let transaction_result =
|
let transaction_result = crate::query_chain_transactions_get_by_signature(
|
||||||
crate::query_chain_transactions_get_by_signature(self.database.as_ref(), signature.as_str())
|
self.database.as_ref(),
|
||||||
|
signature.as_str(),
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
let transaction = match transaction_result {
|
let transaction = match transaction_result {
|
||||||
Ok(Some(transaction)) => transaction,
|
Ok(Some(transaction)) => transaction,
|
||||||
@@ -260,9 +263,8 @@ impl LocalPipelineReplayService {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
let decode_result = dex_decode
|
let decode_result =
|
||||||
.decode_transaction_by_signature(signature.as_str())
|
dex_decode.decode_transaction_by_signature(signature.as_str()).await;
|
||||||
.await;
|
|
||||||
match decode_result {
|
match decode_result {
|
||||||
Ok(decoded_events) => {
|
Ok(decoded_events) => {
|
||||||
result.decoded_event_count += decoded_events.len();
|
result.decoded_event_count += decoded_events.len();
|
||||||
@@ -542,11 +544,8 @@ impl LocalPipelineReplayService {
|
|||||||
signature: &str,
|
signature: &str,
|
||||||
decoded_events: &[crate::DexDecodedEventDto],
|
decoded_events: &[crate::DexDecodedEventDto],
|
||||||
) -> Result<crate::DexDecodeReplayLedgerDto, crate::Error> {
|
) -> Result<crate::DexDecodeReplayLedgerDto, crate::Error> {
|
||||||
let ledger_result = build_success_dex_decode_replay_ledger(
|
let ledger_result =
|
||||||
transaction_id,
|
build_success_dex_decode_replay_ledger(transaction_id, signature, decoded_events);
|
||||||
signature,
|
|
||||||
decoded_events,
|
|
||||||
);
|
|
||||||
let ledger = match ledger_result {
|
let ledger = match ledger_result {
|
||||||
Ok(ledger) => ledger,
|
Ok(ledger) => ledger,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
@@ -597,10 +596,23 @@ fn build_success_dex_decode_replay_ledger(
|
|||||||
Err(error) => {
|
Err(error) => {
|
||||||
return Err(crate::Error::Db(format!(
|
return Err(crate::Error::Db(format!(
|
||||||
"cannot convert decoded event count '{}' to i64: {}",
|
"cannot convert decoded event count '{}' to i64: {}",
|
||||||
decoded_events.len(), error
|
decoded_events.len(),
|
||||||
|
error
|
||||||
)));
|
)));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
let effective_event_count_usize = count_effective_decoded_events(decoded_events);
|
||||||
|
let effective_event_count_result = i64::try_from(effective_event_count_usize);
|
||||||
|
let effective_event_count = match effective_event_count_result {
|
||||||
|
Ok(effective_event_count) => effective_event_count,
|
||||||
|
Err(error) => {
|
||||||
|
return Err(crate::Error::Db(format!(
|
||||||
|
"cannot convert effective decoded event count '{}' to i64: {}",
|
||||||
|
effective_event_count_usize, error
|
||||||
|
)));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let instruction_audit_count = event_count - effective_event_count;
|
||||||
let distinct_token_mint_count_usize = count_distinct_decoded_event_token_mints(decoded_events);
|
let distinct_token_mint_count_usize = count_distinct_decoded_event_token_mints(decoded_events);
|
||||||
let distinct_token_mint_count_result = i64::try_from(distinct_token_mint_count_usize);
|
let distinct_token_mint_count_result = i64::try_from(distinct_token_mint_count_usize);
|
||||||
let distinct_token_mint_count = match distinct_token_mint_count_result {
|
let distinct_token_mint_count = match distinct_token_mint_count_result {
|
||||||
@@ -612,7 +624,7 @@ fn build_success_dex_decode_replay_ledger(
|
|||||||
)));
|
)));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let force_replay_required = event_count > 1 || distinct_token_mint_count > 2;
|
let force_replay_required = effective_event_count > 1 || distinct_token_mint_count > 2;
|
||||||
let decode_status = if event_count == 0 {
|
let decode_status = if event_count == 0 {
|
||||||
crate::DexDecodeReplayLedgerDto::STATUS_NO_EVENTS.to_string()
|
crate::DexDecodeReplayLedgerDto::STATUS_NO_EVENTS.to_string()
|
||||||
} else {
|
} else {
|
||||||
@@ -625,6 +637,8 @@ fn build_success_dex_decode_replay_ledger(
|
|||||||
};
|
};
|
||||||
let status_reason = build_dex_decode_replay_ledger_status_reason(
|
let status_reason = build_dex_decode_replay_ledger_status_reason(
|
||||||
event_count,
|
event_count,
|
||||||
|
effective_event_count,
|
||||||
|
instruction_audit_count,
|
||||||
distinct_token_mint_count,
|
distinct_token_mint_count,
|
||||||
force_replay_required,
|
force_replay_required,
|
||||||
);
|
);
|
||||||
@@ -642,9 +656,22 @@ fn build_success_dex_decode_replay_ledger(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn count_distinct_decoded_event_token_mints(
|
fn count_effective_decoded_events(decoded_events: &[crate::DexDecodedEventDto]) -> usize {
|
||||||
decoded_events: &[crate::DexDecodedEventDto],
|
let mut count = 0_usize;
|
||||||
) -> usize {
|
for event in decoded_events {
|
||||||
|
if is_instruction_audit_event(event) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_instruction_audit_event(event: &crate::DexDecodedEventDto) -> bool {
|
||||||
|
return event.event_kind.ends_with(".instruction_audit");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_distinct_decoded_event_token_mints(decoded_events: &[crate::DexDecodedEventDto]) -> usize {
|
||||||
let mut mints = std::collections::BTreeSet::<std::string::String>::new();
|
let mut mints = std::collections::BTreeSet::<std::string::String>::new();
|
||||||
for event in decoded_events {
|
for event in decoded_events {
|
||||||
insert_optional_mint(&mut mints, &event.lp_mint);
|
insert_optional_mint(&mut mints, &event.lp_mint);
|
||||||
@@ -668,6 +695,8 @@ fn insert_optional_mint(
|
|||||||
|
|
||||||
fn build_dex_decode_replay_ledger_status_reason(
|
fn build_dex_decode_replay_ledger_status_reason(
|
||||||
event_count: i64,
|
event_count: i64,
|
||||||
|
effective_event_count: i64,
|
||||||
|
instruction_audit_count: i64,
|
||||||
distinct_token_mint_count: i64,
|
distinct_token_mint_count: i64,
|
||||||
force_replay_required: bool,
|
force_replay_required: bool,
|
||||||
) -> std::string::String {
|
) -> std::string::String {
|
||||||
@@ -676,11 +705,11 @@ fn build_dex_decode_replay_ledger_status_reason(
|
|||||||
}
|
}
|
||||||
if force_replay_required {
|
if force_replay_required {
|
||||||
return format!(
|
return format!(
|
||||||
"decode completed but remains unsafe for skip: event_count={event_count}, distinct_token_mint_count={distinct_token_mint_count}"
|
"decode completed but remains unsafe for skip: event_count={event_count}, effective_event_count={effective_event_count}, instruction_audit_count={instruction_audit_count}, distinct_token_mint_count={distinct_token_mint_count}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return format!(
|
return format!(
|
||||||
"decode completed and certified for skip: event_count={event_count}, distinct_token_mint_count={distinct_token_mint_count}"
|
"decode completed and certified for skip: event_count={event_count}, effective_event_count={effective_event_count}, instruction_audit_count={instruction_audit_count}, distinct_token_mint_count={distinct_token_mint_count}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -692,3 +721,57 @@ pub async fn replay_local_pipeline(
|
|||||||
let service = crate::LocalPipelineReplayService::new(database);
|
let service = crate::LocalPipelineReplayService::new(database);
|
||||||
return service.replay_local_pipeline(config).await;
|
return service.replay_local_pipeline(config).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
fn make_decoded_event(
|
||||||
|
event_kind: &str,
|
||||||
|
token_a_mint: std::option::Option<&str>,
|
||||||
|
token_b_mint: std::option::Option<&str>,
|
||||||
|
) -> crate::DexDecodedEventDto {
|
||||||
|
return crate::DexDecodedEventDto::new(
|
||||||
|
1,
|
||||||
|
Some(10),
|
||||||
|
"meteora_dlmm".to_string(),
|
||||||
|
crate::METEORA_DLMM_PROGRAM_ID.to_string(),
|
||||||
|
event_kind.to_string(),
|
||||||
|
Some("pool".to_string()),
|
||||||
|
None,
|
||||||
|
token_a_mint.map(|value| return value.to_string()),
|
||||||
|
token_b_mint.map(|value| return value.to_string()),
|
||||||
|
None,
|
||||||
|
"{}".to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ledger_certifies_one_effective_event_with_instruction_audits() {
|
||||||
|
let events = vec![
|
||||||
|
make_decoded_event("meteora_dlmm.swap", Some("mint-a"), Some("mint-b")),
|
||||||
|
make_decoded_event("meteora_dlmm.instruction_audit", None, None),
|
||||||
|
make_decoded_event("meteora_dlmm.instruction_audit", None, None),
|
||||||
|
];
|
||||||
|
let ledger = super::build_success_dex_decode_replay_ledger(1, "sig", events.as_slice())
|
||||||
|
.expect("ledger must build");
|
||||||
|
assert_eq!(ledger.event_count, 3);
|
||||||
|
assert_eq!(ledger.distinct_token_mint_count, 2);
|
||||||
|
assert!(!ledger.force_replay_required);
|
||||||
|
assert_eq!(ledger.certainty, crate::DexDecodeReplayLedgerDto::CERTAINTY_SURE);
|
||||||
|
assert!(ledger.can_skip_decode());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ledger_keeps_multiple_effective_events_unsafe() {
|
||||||
|
let events = vec![
|
||||||
|
make_decoded_event("meteora_dlmm.swap", Some("mint-a"), Some("mint-b")),
|
||||||
|
make_decoded_event("meteora_dlmm.swap", Some("mint-a"), Some("mint-b")),
|
||||||
|
make_decoded_event("meteora_dlmm.instruction_audit", None, None),
|
||||||
|
];
|
||||||
|
let ledger = super::build_success_dex_decode_replay_ledger(1, "sig", events.as_slice())
|
||||||
|
.expect("ledger must build");
|
||||||
|
assert_eq!(ledger.event_count, 3);
|
||||||
|
assert!(ledger.force_replay_required);
|
||||||
|
assert_eq!(ledger.certainty, crate::DexDecodeReplayLedgerDto::CERTAINTY_UNSAFE);
|
||||||
|
assert!(!ledger.can_skip_decode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user