v0.2.7-pre.014
This commit is contained in:
135
crates/ksp-core-lib/README.md
Normal file
135
crates/ksp-core-lib/README.md
Normal file
@@ -0,0 +1,135 @@
|
||||
<!-- file: crates/ksp-core-lib/README.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# `ksp-core-lib`
|
||||
|
||||
`ksp-core-lib` porte les contrats fondamentaux partagés par les couches KSP sans dépendre des domaines de plus haut niveau.
|
||||
|
||||
La crate possède actuellement trois responsabilités :
|
||||
|
||||
- le type d'erreur commun KSP ;
|
||||
- le type Solana `Pubkey` réexporté comme primitive d'adresse canonique ;
|
||||
- le registre KSP des Program IDs Solana fondamentaux et leur taxonomie.
|
||||
|
||||
## Frontière architecturale
|
||||
|
||||
Core reste une couche basse. Elle ne possède ni configuration, ni logging runtime, ni transport réseau, ni wallet, ni stockage, ni logique de décodage/exécution.
|
||||
|
||||
Les crates de niveau supérieur peuvent dépendre de Core et réutiliser ses contrats ; Core ne doit pas introduire de dépendance inverse vers ces couches.
|
||||
|
||||
La seule dépendance runtime externe directe actuelle est `solana-pubkey`, utilisée pour la primitive `Pubkey`.
|
||||
|
||||
## Erreur commune KSP
|
||||
|
||||
Le contrat d'erreur public repose sur :
|
||||
|
||||
```text
|
||||
ErrorCode
|
||||
ErrorContext
|
||||
Error
|
||||
Result<T>
|
||||
```
|
||||
|
||||
`ErrorCode` sépare un `domain` stable d'un `code` stable. `Error` ajoute :
|
||||
|
||||
- un message humain ;
|
||||
- des champs de contexte ordonnés ;
|
||||
- une source d'erreur standard optionnelle compatible `Send + Sync`.
|
||||
|
||||
L'affichage d'une erreur reste compact :
|
||||
|
||||
```text
|
||||
<domain>.<code>: <message>
|
||||
```
|
||||
|
||||
Les consommateurs ajoutent uniquement des contextes sûrs. Les secrets, credentials, key material, URLs sensibles ou payloads massifs ne doivent pas être copiés dans le message ou le contexte d'une erreur.
|
||||
|
||||
## `Pubkey`
|
||||
|
||||
La crate réexporte :
|
||||
|
||||
```rust
|
||||
ksp_core_lib::Pubkey
|
||||
```
|
||||
|
||||
Les autres crates KSP utilisent cette primitive lorsqu'un contrat public a besoin d'une adresse Solana générique. Elles évitent ainsi de multiplier les propriétaires de type pour la même notion.
|
||||
|
||||
## Program IDs fondamentaux
|
||||
|
||||
Core possède 18 Program IDs Solana fondamentaux sous deux formes cohérentes :
|
||||
|
||||
```text
|
||||
PRGID_* -> Base58 &str
|
||||
PRGIDPK_* -> Pubkey typé
|
||||
```
|
||||
|
||||
Les deux formes sont déclarées depuis une même valeur grâce à `declare_program_id!`.
|
||||
|
||||
Le registre couvre notamment :
|
||||
|
||||
- System, Vote, Stake, Config et Feature ;
|
||||
- Address Lookup Table et Compute Budget ;
|
||||
- les loaders natif, BPF v1, BPF v2, BPF upgradeable et Loader v4 ;
|
||||
- les precompiles Ed25519, Secp256k1 et Secp256r1 ;
|
||||
- Slashing ;
|
||||
- ZK ElGamal Proof et ZK Token Proof.
|
||||
|
||||
Le registre n'est pas un catalogue général de tous les programmes Solana. Les protocoles, applications et Program IDs de domaines futurs sont ajoutés dans les couches propriétaires appropriées lorsqu'un besoin réel existe.
|
||||
|
||||
## Registre et taxonomie
|
||||
|
||||
Chaque entrée est exposée sous `ProgramIdEntry` avec :
|
||||
|
||||
```text
|
||||
code
|
||||
name
|
||||
program_id
|
||||
pubkey
|
||||
domain
|
||||
family
|
||||
protocol
|
||||
subfamily
|
||||
program_version
|
||||
kind
|
||||
```
|
||||
|
||||
`ProgramIdKind` distingue actuellement :
|
||||
|
||||
```text
|
||||
Program
|
||||
Loader
|
||||
Precompile
|
||||
EnshrinedProgram
|
||||
```
|
||||
|
||||
La lecture du registre s'effectue par :
|
||||
|
||||
```text
|
||||
entries()
|
||||
native_program_ids()
|
||||
program_ids(filter)
|
||||
program_ids_by_domain(...)
|
||||
program_ids_by_family(...)
|
||||
program_ids_by_protocol(...)
|
||||
find_program_id(...)
|
||||
find_program_pubkey(...)
|
||||
```
|
||||
|
||||
`ProgramIdFilter` permet de combiner les axes `domain`, `family`, `protocol`, `subfamily`, `program_version` et `kind`.
|
||||
|
||||
## Garanties validées
|
||||
|
||||
Les tests publics et unitaires verrouillent notamment :
|
||||
|
||||
- la correspondance Base58 / `Pubkey` des constantes ;
|
||||
- l'unicité des codes, Program IDs et pubkeys du registre ;
|
||||
- les 18 entrées fondamentales ;
|
||||
- les recherches textuelles et typées ;
|
||||
- les vues et filtres taxonomiques ;
|
||||
- l'absence des comptes well-known qui ne sont pas des programmes ;
|
||||
- le contrat `Error`, son ordre de contexte et sa chaîne `source()` ;
|
||||
- `Error: Send + Sync`.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [`USAGE.md`](USAGE.md) — exemples d'utilisation de l'erreur commune, de `Pubkey`, des Program IDs et du registre.
|
||||
265
crates/ksp-core-lib/USAGE.md
Normal file
265
crates/ksp-core-lib/USAGE.md
Normal file
@@ -0,0 +1,265 @@
|
||||
<!-- file: crates/ksp-core-lib/USAGE.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Utilisation de `ksp-core-lib`
|
||||
|
||||
## Utiliser le type d'erreur commun
|
||||
|
||||
Une crate KSP définit des codes stables puis retourne `ksp_core_lib::Result<T>` :
|
||||
|
||||
```rust
|
||||
const ERROR_CODE_LOAD_FAILED: ksp_core_lib::ErrorCode =
|
||||
ksp_core_lib::ErrorCode::new("store", "load_failed");
|
||||
|
||||
fn load_value() -> ksp_core_lib::Result<u64> {
|
||||
let error = ksp_core_lib::Error::new(
|
||||
ERROR_CODE_LOAD_FAILED,
|
||||
"unable to load value",
|
||||
)
|
||||
.with_context("operation", "load_value");
|
||||
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
```
|
||||
|
||||
Le domaine et le code sont accessibles séparément :
|
||||
|
||||
```rust
|
||||
let code = ERROR_CODE_LOAD_FAILED;
|
||||
assert_eq!(code.domain(), "store");
|
||||
assert_eq!(code.code(), "load_failed");
|
||||
```
|
||||
|
||||
Le message et les champs de contexte restent accessibles sans parser `Display` :
|
||||
|
||||
```rust
|
||||
let error = ksp_core_lib::Error::new(
|
||||
ERROR_CODE_LOAD_FAILED,
|
||||
"unable to load value",
|
||||
)
|
||||
.with_context("component", "postgres")
|
||||
.with_context("operation", "load_value");
|
||||
|
||||
assert_eq!(error.code(), ERROR_CODE_LOAD_FAILED);
|
||||
assert_eq!(error.message(), "unable to load value");
|
||||
assert_eq!(error.context()[0].key(), "component");
|
||||
assert_eq!(error.context()[0].value(), "postgres");
|
||||
```
|
||||
|
||||
Un `ErrorContext` peut aussi être construit indépendamment lorsqu’un caller prépare explicitement son contexte :
|
||||
|
||||
```rust
|
||||
let context = ksp_core_lib::ErrorContext::new(
|
||||
"operation",
|
||||
"load_value",
|
||||
);
|
||||
|
||||
assert_eq!(context.key(), "operation");
|
||||
assert_eq!(context.value(), "load_value");
|
||||
```
|
||||
|
||||
Les valeurs de contexte doivent être sûres à exposer. Ne pas y placer de secret, credential, seed, key material, URL contenant une clé API ou payload brut volumineux.
|
||||
|
||||
## Conserver une erreur source
|
||||
|
||||
Une erreur externe compatible `Send + Sync + 'static` peut rester dans la chaîne standard :
|
||||
|
||||
```rust
|
||||
#[derive(Debug)]
|
||||
struct SourceError;
|
||||
|
||||
impl std::fmt::Display for SourceError {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
return formatter.write_str("source failure");
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for SourceError {}
|
||||
|
||||
let error = ksp_core_lib::Error::new(
|
||||
ERROR_CODE_LOAD_FAILED,
|
||||
"unable to load value",
|
||||
)
|
||||
.with_source(SourceError);
|
||||
|
||||
let source = std::error::Error::source(&error);
|
||||
assert!(source.is_some());
|
||||
```
|
||||
|
||||
La source sert au chaînage d'erreurs ; son contenu ne doit pas être recopié sans contrôle dans un contexte ou un log public.
|
||||
|
||||
## Utiliser `Pubkey`
|
||||
|
||||
Core réexporte la primitive Solana utilisée par les contrats KSP :
|
||||
|
||||
```rust
|
||||
let system = ksp_core_lib::Pubkey::from_str_const(
|
||||
ksp_core_lib::PRGID_SOLANA_SYSTEM,
|
||||
);
|
||||
|
||||
assert_eq!(system, ksp_core_lib::PRGIDPK_SOLANA_SYSTEM);
|
||||
```
|
||||
|
||||
Une crate consommatrice peut donc utiliser `ksp_core_lib::Pubkey` dans sa propre API sans dépendre directement de `solana-pubkey` lorsque Core est déjà le propriétaire architectural de cette primitive.
|
||||
|
||||
## Utiliser les constantes Program ID
|
||||
|
||||
Chaque Program ID Core possède une forme texte et une forme typée :
|
||||
|
||||
```rust
|
||||
let system_text: &str = ksp_core_lib::PRGID_SOLANA_SYSTEM;
|
||||
let system_pubkey: ksp_core_lib::Pubkey =
|
||||
ksp_core_lib::PRGIDPK_SOLANA_SYSTEM;
|
||||
|
||||
assert_eq!(
|
||||
system_pubkey,
|
||||
ksp_core_lib::Pubkey::from_str_const(system_text),
|
||||
);
|
||||
```
|
||||
|
||||
Les constantes `PRGID_*` sont utiles pour les wires, diagnostics sûrs ou comparaisons texte. Les constantes `PRGIDPK_*` sont préférées dès qu'un contrat manipule une adresse Solana typée.
|
||||
|
||||
## Déclarer une paire texte / `Pubkey`
|
||||
|
||||
`declare_program_id!` permet à une couche KSP propriétaire d'un Program ID de déclarer les deux représentations depuis une seule valeur Base58 :
|
||||
|
||||
```rust
|
||||
ksp_core_lib::declare_program_id!(
|
||||
PRGID_EXAMPLE,
|
||||
PRGIDPK_EXAMPLE,
|
||||
"11111111111111111111111111111111"
|
||||
);
|
||||
|
||||
assert_eq!(PRGID_EXAMPLE, ksp_core_lib::PRGID_SOLANA_SYSTEM);
|
||||
assert_eq!(PRGIDPK_EXAMPLE, ksp_core_lib::PRGIDPK_SOLANA_SYSTEM);
|
||||
```
|
||||
|
||||
La macro ne signifie pas que toute nouvelle constante doit être ajoutée au registre Core. La couche propriétaire du domaine décide où vit le nouveau Program ID.
|
||||
|
||||
## Parcourir le registre canonique
|
||||
|
||||
`entries()` retourne le registre complet en ordre déterministe :
|
||||
|
||||
```rust
|
||||
for entry in ksp_core_lib::entries() {
|
||||
let code = entry.code();
|
||||
let name = entry.name();
|
||||
let text = entry.program_id();
|
||||
let pubkey = entry.pubkey();
|
||||
let domain = entry.domain();
|
||||
let family = entry.family();
|
||||
let protocol = entry.protocol();
|
||||
let subfamily = entry.subfamily();
|
||||
let program_version = entry.program_version();
|
||||
let kind = entry.kind();
|
||||
|
||||
let _ = (
|
||||
code,
|
||||
name,
|
||||
text,
|
||||
pubkey,
|
||||
domain,
|
||||
family,
|
||||
protocol,
|
||||
subfamily,
|
||||
program_version,
|
||||
kind,
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
`native_program_ids()` fournit la vue des Program IDs Solana fondamentaux actuellement enregistrés :
|
||||
|
||||
```rust
|
||||
let count = ksp_core_lib::native_program_ids().count();
|
||||
assert_eq!(count, 18);
|
||||
```
|
||||
|
||||
## Rechercher une entrée
|
||||
|
||||
Recherche par représentation Base58 :
|
||||
|
||||
```rust
|
||||
let entry = ksp_core_lib::find_program_id(
|
||||
ksp_core_lib::PRGID_SOLANA_SYSTEM,
|
||||
);
|
||||
|
||||
let entry = match entry {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
|
||||
assert_eq!(entry.code(), "solana.system");
|
||||
```
|
||||
|
||||
Recherche par `Pubkey` :
|
||||
|
||||
```rust
|
||||
let entry = ksp_core_lib::find_program_pubkey(
|
||||
&ksp_core_lib::PRGIDPK_SOLANA_VOTE,
|
||||
);
|
||||
|
||||
assert!(entry.is_some());
|
||||
```
|
||||
|
||||
Une recherche inconnue retourne `None`; le registre n'invente pas une entrée générique.
|
||||
|
||||
## Filtrer par axe simple
|
||||
|
||||
Les helpers spécialisés conviennent aux filtres simples :
|
||||
|
||||
```rust
|
||||
let loaders = ksp_core_lib::program_ids_by_family("loader");
|
||||
for loader in loaders {
|
||||
assert_eq!(loader.family(), "loader");
|
||||
}
|
||||
```
|
||||
|
||||
Les vues disponibles peuvent être consommées directement :
|
||||
|
||||
```rust
|
||||
let solana_entries = ksp_core_lib::program_ids_by_domain("solana").count();
|
||||
let loaders = ksp_core_lib::program_ids_by_family("loader").count();
|
||||
let solana_protocol = ksp_core_lib::program_ids_by_protocol("solana").count();
|
||||
|
||||
assert_eq!(solana_entries, 18);
|
||||
assert_eq!(loaders, 5);
|
||||
assert_eq!(solana_protocol, 18);
|
||||
```
|
||||
|
||||
## Combiner plusieurs axes
|
||||
|
||||
`ProgramIdFilter` compose les contraintes sans allocation de collection intermédiaire :
|
||||
|
||||
```rust
|
||||
let filter = ksp_core_lib::ProgramIdFilter::new()
|
||||
.with_domain("solana")
|
||||
.with_family("loader")
|
||||
.with_protocol("solana")
|
||||
.with_subfamily("bpf")
|
||||
.with_program_version("v2")
|
||||
.with_kind(ksp_core_lib::ProgramIdKind::Loader);
|
||||
|
||||
for entry in ksp_core_lib::program_ids(filter) {
|
||||
assert_eq!(entry.domain(), "solana");
|
||||
assert_eq!(entry.family(), "loader");
|
||||
assert_eq!(entry.protocol(), "solana");
|
||||
assert_eq!(entry.subfamily(), std::option::Option::Some("bpf"));
|
||||
assert_eq!(entry.program_version(), std::option::Option::Some("v2"));
|
||||
assert_eq!(entry.kind(), ksp_core_lib::ProgramIdKind::Loader);
|
||||
}
|
||||
```
|
||||
|
||||
Un `ProgramIdFilter::new()` vide correspond à toutes les entrées du registre.
|
||||
|
||||
## Choisir entre texte, `Pubkey` et descriptor
|
||||
|
||||
Utiliser :
|
||||
|
||||
```text
|
||||
PRGID_* / &str pour une représentation Base58 canonique
|
||||
PRGIDPK_* / Pubkey pour un contrat Solana typé
|
||||
ProgramIdEntry lorsqu'il faut aussi la taxonomie KSP
|
||||
```
|
||||
|
||||
Ne pas reparcourir les constantes ou reconstruire une taxonomie parallèle dans une crate consommatrice lorsque le registre Core fournit déjà l'information requise.
|
||||
Reference in New Issue
Block a user