v0.2.3-pre.002-fix.001

This commit is contained in:
2026-08-18 11:27:24 +02:00
parent 1ccf11bcc1
commit 1d37f04d63
4 changed files with 247 additions and 5 deletions

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml # file: Cargo.toml
# version: 121 # version: 122
[workspace] [workspace]
resolver = "3" resolver = "3"
members = ["crates/ksp-app-config-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib"] members = ["crates/ksp-app-config-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib"]
[workspace.package] [workspace.package]
version = "0.2.3-pre.2" version = "0.2.3-pre.2.fix.1"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/rpc_transactions.rs // file: crates/ksp-onchain-transport-lib/src/rpc_transactions.rs
// version: 1 // version: 2
/// Binary encoding accepted for serialized transaction input payloads. /// Binary encoding accepted for serialized transaction input payloads.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
@@ -20,6 +20,7 @@ impl SolanaTransactionBinaryEncoding {
}; };
} }
#[cfg(test)]
fn from_wire(value: &str) -> std::option::Option<Self> { fn from_wire(value: &str) -> std::option::Option<Self> {
return match value { return match value {
"base58" => std::option::Option::Some(Self::Base58), "base58" => std::option::Option::Some(Self::Base58),
@@ -159,12 +160,14 @@ impl SolanaGetTransactionConfig {
} }
/// Returns whether the modern config would serialize to an empty object. /// Returns whether the modern config would serialize to an empty object.
#[cfg(test)]
pub(crate) const fn is_empty(&self) -> bool { pub(crate) const fn is_empty(&self) -> bool {
return self.commitment.is_none() && self.encoding.is_none() && self.max_supported_transaction_version.is_none(); return self.commitment.is_none() && self.encoding.is_none() && self.max_supported_transaction_version.is_none();
} }
/// Serializes the modern config to its Solana JSON-RPC wire object. /// Serializes the modern config to its Solana JSON-RPC wire object.
#[must_use] #[must_use]
#[cfg(test)]
pub(crate) fn to_json_value(self) -> serde_json::Value { pub(crate) fn to_json_value(self) -> serde_json::Value {
let mut object = serde_json::Map::new(); let mut object = serde_json::Map::new();
if let std::option::Option::Some(commitment) = self.commitment { if let std::option::Option::Some(commitment) = self.commitment {
@@ -233,12 +236,14 @@ impl SolanaSignaturesForAddressConfig {
} }
/// Returns whether the pagination config would serialize to an empty object. /// Returns whether the pagination config would serialize to an empty object.
#[cfg(test)]
pub(crate) fn is_empty(&self) -> bool { pub(crate) fn is_empty(&self) -> bool {
return self.before.is_none() && self.until.is_none() && self.limit.is_none() && self.commitment().is_none() && self.min_context_slot().is_none(); return self.before.is_none() && self.until.is_none() && self.limit.is_none() && self.commitment().is_none() && self.min_context_slot().is_none();
} }
/// Serializes this pagination config to the Solana JSON-RPC wire object. /// Serializes this pagination config to the Solana JSON-RPC wire object.
#[must_use] #[must_use]
#[cfg(test)]
pub(crate) fn to_json_value(&self) -> serde_json::Value { pub(crate) fn to_json_value(&self) -> serde_json::Value {
let context = self.context.to_json_value(); let context = self.context.to_json_value();
let mut object = match context { let mut object = match context {
@@ -278,12 +283,14 @@ impl SolanaSignatureStatusesConfig {
} }
/// Returns whether the status config would serialize to an empty object. /// Returns whether the status config would serialize to an empty object.
#[cfg(test)]
pub(crate) const fn is_empty(&self) -> bool { pub(crate) const fn is_empty(&self) -> bool {
return self.search_transaction_history.is_none(); return self.search_transaction_history.is_none();
} }
/// Serializes this config to the Solana JSON-RPC wire object. /// Serializes this config to the Solana JSON-RPC wire object.
#[must_use] #[must_use]
#[cfg(test)]
pub(crate) fn to_json_value(self) -> serde_json::Value { pub(crate) fn to_json_value(self) -> serde_json::Value {
let mut object = serde_json::Map::new(); let mut object = serde_json::Map::new();
if let std::option::Option::Some(value) = self.search_transaction_history { if let std::option::Option::Some(value) = self.search_transaction_history {
@@ -320,12 +327,14 @@ impl SolanaRequestAirdropConfig {
} }
/// Returns whether the airdrop config would serialize to an empty object. /// Returns whether the airdrop config would serialize to an empty object.
#[cfg(test)]
pub(crate) fn is_empty(&self) -> bool { pub(crate) fn is_empty(&self) -> bool {
return self.recent_blockhash.is_none() && self.commitment.is_none(); return self.recent_blockhash.is_none() && self.commitment.is_none();
} }
/// Serializes this config to the Solana JSON-RPC wire object. /// Serializes this config to the Solana JSON-RPC wire object.
#[must_use] #[must_use]
#[cfg(test)]
pub(crate) fn to_json_value(&self) -> serde_json::Value { pub(crate) fn to_json_value(&self) -> serde_json::Value {
let mut object = serde_json::Map::new(); let mut object = serde_json::Map::new();
if let std::option::Option::Some(blockhash) = self.recent_blockhash.as_ref() { if let std::option::Option::Some(blockhash) = self.recent_blockhash.as_ref() {
@@ -392,6 +401,7 @@ impl SolanaSendTransactionConfig {
} }
/// Returns whether the send config would serialize to an empty object. /// Returns whether the send config would serialize to an empty object.
#[cfg(test)]
pub(crate) const fn is_empty(&self) -> bool { pub(crate) const fn is_empty(&self) -> bool {
return self.skip_preflight.is_none() return self.skip_preflight.is_none()
&& self.preflight_commitment.is_none() && self.preflight_commitment.is_none()
@@ -402,6 +412,7 @@ impl SolanaSendTransactionConfig {
/// Serializes this config to the Solana JSON-RPC wire object. /// Serializes this config to the Solana JSON-RPC wire object.
#[must_use] #[must_use]
#[cfg(test)]
pub(crate) fn to_json_value(self) -> serde_json::Value { pub(crate) fn to_json_value(self) -> serde_json::Value {
let mut object = serde_json::Map::new(); let mut object = serde_json::Map::new();
if let std::option::Option::Some(value) = self.skip_preflight { if let std::option::Option::Some(value) = self.skip_preflight {
@@ -451,12 +462,16 @@ impl SolanaSimulationAccountsConfig {
/// Serializes the nested account config to the Solana JSON-RPC wire object. /// Serializes the nested account config to the Solana JSON-RPC wire object.
#[must_use] #[must_use]
#[cfg(test)]
pub(crate) fn to_json_value(&self) -> serde_json::Value { pub(crate) fn to_json_value(&self) -> serde_json::Value {
let mut object = serde_json::Map::new(); let mut object = serde_json::Map::new();
if let std::option::Option::Some(encoding) = self.encoding { if let std::option::Option::Some(encoding) = self.encoding {
object.insert("encoding".to_owned(), serde_json::Value::String(encoding.as_str().to_owned())); object.insert("encoding".to_owned(), serde_json::Value::String(encoding.as_str().to_owned()));
} }
let addresses = self.addresses.iter().map(|address| address.to_string()).map(serde_json::Value::String).collect::<std::vec::Vec<_>>(); let mut addresses = std::vec::Vec::with_capacity(self.addresses.len());
for address in &self.addresses {
addresses.push(serde_json::Value::String(address.to_string()));
}
object.insert("addresses".to_owned(), serde_json::Value::Array(addresses)); object.insert("addresses".to_owned(), serde_json::Value::Array(addresses));
return serde_json::Value::Object(object); return serde_json::Value::Object(object);
} }
@@ -540,6 +555,7 @@ impl SolanaSimulateTransactionConfig {
} }
/// Returns whether the simulation config would serialize to an empty object. /// Returns whether the simulation config would serialize to an empty object.
#[cfg(test)]
pub(crate) fn is_empty(&self) -> bool { pub(crate) fn is_empty(&self) -> bool {
return self.commitment.is_none() return self.commitment.is_none()
&& self.encoding.is_none() && self.encoding.is_none()
@@ -552,6 +568,7 @@ impl SolanaSimulateTransactionConfig {
/// Serializes this config to the Solana JSON-RPC wire object. /// Serializes this config to the Solana JSON-RPC wire object.
#[must_use] #[must_use]
#[cfg(test)]
pub(crate) fn to_json_value(&self) -> serde_json::Value { pub(crate) fn to_json_value(&self) -> serde_json::Value {
let mut object = serde_json::Map::new(); let mut object = serde_json::Map::new();
if let std::option::Option::Some(commitment) = self.commitment { if let std::option::Option::Some(commitment) = self.commitment {
@@ -600,6 +617,7 @@ impl SolanaLatestBlockhash {
} }
/// Decodes a latest-blockhash object from the Solana JSON wire shape. /// Decodes a latest-blockhash object from the Solana JSON wire shape.
#[cfg(test)]
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> { pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireLatestBlockhash>(method, value); let decoded = crate::decode_wire_json::<WireLatestBlockhash>(method, value);
let wire = match decoded { let wire = match decoded {
@@ -634,6 +652,7 @@ impl SolanaPrioritizationFee {
} }
/// Decodes one prioritization-fee sample from the Solana JSON wire shape. /// Decodes one prioritization-fee sample from the Solana JSON wire shape.
#[cfg(test)]
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> { pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WirePrioritizationFee>(method, value); let decoded = crate::decode_wire_json::<WirePrioritizationFee>(method, value);
return match decoded { return match decoded {
@@ -710,6 +729,7 @@ impl SolanaSignatureInfo {
} }
/// Decodes one signature record from the Solana JSON wire shape. /// Decodes one signature record from the Solana JSON wire shape.
#[cfg(test)]
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> { pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireSignatureInfo>(method, value); let decoded = crate::decode_wire_json::<WireSignatureInfo>(method, value);
let wire = match decoded { let wire = match decoded {
@@ -775,6 +795,7 @@ impl SolanaSignatureStatus {
} }
/// Decodes one present signature status from the Solana JSON wire shape. /// Decodes one present signature status from the Solana JSON wire shape.
#[cfg(test)]
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> { pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireSignatureStatus>(method, value); let decoded = crate::decode_wire_json::<WireSignatureStatus>(method, value);
let wire = match decoded { let wire = match decoded {
@@ -814,6 +835,7 @@ pub enum SolanaEncodedTransaction {
impl SolanaEncodedTransaction { impl SolanaEncodedTransaction {
/// Decodes the untagged transaction wire union without decoding transaction bytes. /// Decodes the untagged transaction wire union without decoding transaction bytes.
#[cfg(test)]
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> { pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
return match value { return match value {
serde_json::Value::String(data) => std::result::Result::Ok(Self::LegacyBinary(data)), serde_json::Value::String(data) => std::result::Result::Ok(Self::LegacyBinary(data)),
@@ -834,6 +856,7 @@ pub enum SolanaTransactionVersion {
} }
impl SolanaTransactionVersion { impl SolanaTransactionVersion {
#[cfg(test)]
fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> { fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
return match value { return match value {
serde_json::Value::String(value) if value == "legacy" => std::result::Result::Ok(Self::Legacy), serde_json::Value::String(value) if value == "legacy" => std::result::Result::Ok(Self::Legacy),
@@ -911,6 +934,7 @@ impl SolanaConfirmedTransaction {
} }
/// Decodes one non-null confirmed transaction without decoding Program-specific transaction internals. /// Decodes one non-null confirmed transaction without decoding Program-specific transaction internals.
#[cfg(test)]
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> { pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireConfirmedTransaction>(method, value); let decoded = crate::decode_wire_json::<WireConfirmedTransaction>(method, value);
let wire = match decoded { let wire = match decoded {
@@ -1043,6 +1067,7 @@ impl SolanaSimulateTransactionResult {
} }
/// Decodes a simulation result while preserving optional/nullable current Agave fields. /// Decodes a simulation result while preserving optional/nullable current Agave fields.
#[cfg(test)]
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> { pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireSimulateTransactionResult>(method, value); let decoded = crate::decode_wire_json::<WireSimulateTransactionResult>(method, value);
let wire = match decoded { let wire = match decoded {
@@ -1078,10 +1103,12 @@ impl SolanaSimulateTransactionResult {
} }
} }
#[cfg(test)]
fn invalid_transaction_wire(method: &str, field: &str, message: &'static str) -> ksp_core_lib::Error { fn invalid_transaction_wire(method: &str, field: &str, message: &'static str) -> ksp_core_lib::Error {
return ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, message).with_context("rpc_method", method).with_context("field", field); return ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, message).with_context("rpc_method", method).with_context("field", field);
} }
#[cfg(test)]
fn decode_confirmation_status( fn decode_confirmation_status(
method: &str, method: &str,
field: &str, field: &str,
@@ -1100,6 +1127,7 @@ fn decode_confirmation_status(
return std::result::Result::Ok(std::option::Option::Some(status)); return std::result::Result::Ok(std::option::Option::Some(status));
} }
#[cfg(test)]
fn decode_binary_transaction_tuple(method: &str, values: std::vec::Vec<serde_json::Value>) -> ksp_core_lib::Result<crate::SolanaEncodedTransaction> { fn decode_binary_transaction_tuple(method: &str, values: std::vec::Vec<serde_json::Value>) -> ksp_core_lib::Result<crate::SolanaEncodedTransaction> {
if values.len() != 2 { if values.len() != 2 {
return std::result::Result::Err(invalid_transaction_wire(method, "transaction", "encoded transaction tuple must contain data and encoding")); return std::result::Result::Err(invalid_transaction_wire(method, "transaction", "encoded transaction tuple must contain data and encoding"));
@@ -1139,6 +1167,7 @@ fn decode_binary_transaction_tuple(method: &str, values: std::vec::Vec<serde_jso
return std::result::Result::Ok(crate::SolanaEncodedTransaction::Binary { data, encoding }); return std::result::Result::Ok(crate::SolanaEncodedTransaction::Binary { data, encoding });
} }
#[cfg(test)]
fn decode_transaction_version_field( fn decode_transaction_version_field(
method: &str, method: &str,
field: crate::SolanaWireField<serde_json::Value>, field: crate::SolanaWireField<serde_json::Value>,
@@ -1156,6 +1185,7 @@ fn decode_transaction_version_field(
}; };
} }
#[cfg(test)]
fn decode_simulation_accounts_field( fn decode_simulation_accounts_field(
method: &str, method: &str,
field: crate::SolanaWireField<std::vec::Vec<std::option::Option<serde_json::Value>>>, field: crate::SolanaWireField<std::vec::Vec<std::option::Option<serde_json::Value>>>,
@@ -1183,6 +1213,7 @@ fn decode_simulation_accounts_field(
return std::result::Result::Ok(crate::SolanaWireField::Value(accounts)); return std::result::Result::Ok(crate::SolanaWireField::Value(accounts));
} }
#[cfg(test)]
fn decode_replacement_blockhash_field( fn decode_replacement_blockhash_field(
method: &str, method: &str,
field: crate::SolanaWireField<serde_json::Value>, field: crate::SolanaWireField<serde_json::Value>,
@@ -1199,6 +1230,7 @@ fn decode_replacement_blockhash_field(
}; };
} }
#[cfg(test)]
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct WireLatestBlockhash { struct WireLatestBlockhash {
@@ -1206,6 +1238,7 @@ struct WireLatestBlockhash {
last_valid_block_height: u64, last_valid_block_height: u64,
} }
#[cfg(test)]
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct WirePrioritizationFee { struct WirePrioritizationFee {
@@ -1213,6 +1246,7 @@ struct WirePrioritizationFee {
prioritization_fee: u64, prioritization_fee: u64,
} }
#[cfg(test)]
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct WireSignatureInfo { struct WireSignatureInfo {
@@ -1230,6 +1264,7 @@ struct WireSignatureInfo {
transaction_index: std::option::Option<u32>, transaction_index: std::option::Option<u32>,
} }
#[cfg(test)]
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct WireSignatureStatus { struct WireSignatureStatus {
@@ -1243,6 +1278,7 @@ struct WireSignatureStatus {
confirmation_status: std::option::Option<std::string::String>, confirmation_status: std::option::Option<std::string::String>,
} }
#[cfg(test)]
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct WireConfirmedTransaction { struct WireConfirmedTransaction {
@@ -1258,6 +1294,7 @@ struct WireConfirmedTransaction {
transaction_index: crate::SolanaWireField<u32>, transaction_index: crate::SolanaWireField<u32>,
} }
#[cfg(test)]
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct WireSimulateTransactionResult { struct WireSimulateTransactionResult {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_transactions.rs // file: crates/ksp-onchain-transport-lib/unit_tests/rpc_transactions.rs
// version: 1 // version: 2
#[test] #[test]
fn transaction_encoding_strings_match_current_and_legacy_wire_labels() { fn transaction_encoding_strings_match_current_and_legacy_wire_labels() {
@@ -75,15 +75,19 @@ fn signatures_for_address_config_preserves_pagination_and_context_order_independ
#[test] #[test]
fn signature_status_and_airdrop_configs_preserve_explicit_options() { fn signature_status_and_airdrop_configs_preserve_explicit_options() {
let status = crate::SolanaSignatureStatusesConfig::new(std::option::Option::Some(false)); let status = crate::SolanaSignatureStatusesConfig::new(std::option::Option::Some(false));
assert!(!status.is_empty());
assert_eq!(status.search_transaction_history(), std::option::Option::Some(false)); assert_eq!(status.search_transaction_history(), std::option::Option::Some(false));
assert_eq!(status.to_json_value(), serde_json::json!({"searchTransactionHistory":false})); assert_eq!(status.to_json_value(), serde_json::json!({"searchTransactionHistory":false}));
assert!(crate::SolanaSignatureStatusesConfig::default().is_empty());
let airdrop = crate::SolanaRequestAirdropConfig::new( let airdrop = crate::SolanaRequestAirdropConfig::new(
std::option::Option::Some("recent-blockhash".to_owned()), std::option::Option::Some("recent-blockhash".to_owned()),
std::option::Option::Some(crate::SolanaCommitment::Confirmed), std::option::Option::Some(crate::SolanaCommitment::Confirmed),
); );
assert!(!airdrop.is_empty());
assert_eq!(airdrop.recent_blockhash(), std::option::Option::Some("recent-blockhash")); assert_eq!(airdrop.recent_blockhash(), std::option::Option::Some("recent-blockhash"));
assert_eq!(airdrop.commitment(), std::option::Option::Some(crate::SolanaCommitment::Confirmed)); assert_eq!(airdrop.commitment(), std::option::Option::Some(crate::SolanaCommitment::Confirmed));
assert_eq!(airdrop.to_json_value(), serde_json::json!({"recentBlockhash":"recent-blockhash","commitment":"confirmed"})); assert_eq!(airdrop.to_json_value(), serde_json::json!({"recentBlockhash":"recent-blockhash","commitment":"confirmed"}));
assert!(crate::SolanaRequestAirdropConfig::default().is_empty());
} }
#[test] #[test]

View File

@@ -0,0 +1,201 @@
<!-- file: deltas/0.2.3/pre.002-fix.001.md -->
<!-- version: 1 -->
# Delta `0.2.3-pre.002-fix.001` — Clippy et helpers wire préparatoires
## Base requise
Ce correctif s'applique après :
```text
0.2.3-pre.002
workspace.package.version = "0.2.3-pre.2"
```
Le commit de livraison attendu pour la base est :
```text
v0.2.3-pre.002
```
## Motif du fix
Les validations opérateur du 2026-08-18 ont établi :
```text
cargo fmt --all OK
cargo check --workspace OK avec warnings dead_code
cargo clippy --workspace --all-targets ÉCHEC
cargo test -p ksp-onchain-transport-lib OK
```
Le test Transport a confirmé :
```text
145 unit tests OK
14 public API tests OK
8 release completeness OK
1 smoke Devnet ignored / opt-in
0 échec
```
Le défaut bloquant est `clippy::implicit-return` dans la sérialisation des adresses de `SolanaSimulationAccountsConfig` :
```text
.map(|address| address.to_string())
```
La policy Clippy du workspace exige un `return` explicite y compris dans cette closure.
La même validation a aussi révélé 28 warnings `dead_code` sur les helpers `pub(crate)` / privés préparés par `pre.002` pour les futurs wrappers Transactions. Ces helpers sont exercés par les tests unitaires de `pre.002`, mais n'ont volontairement encore aucun caller dans la bibliothèque non-test puisque les wrappers ne commencent qu'en `pre.003`.
Le fix traite les deux problèmes avant d'ouvrir `pre.003`.
## Version technique
Ce correctif modifie une source Rust de production et une source Rust de test. Conformément au signal technique KSP :
```text
0.2.3-pre.2 -> 0.2.3-pre.2.fix.1
```
Le header du `Cargo.toml` passe de `version: 121` à `version: 122`.
Le commit de livraison attendu est :
```text
v0.2.3-pre.002-fix.001
```
## Correction `clippy::implicit-return`
La conversion des adresses de simulation n'utilise plus une closure `map` dont la dernière expression constitue un retour implicite.
La construction devient volontairement explicite :
```rust
let mut addresses = std::vec::Vec::with_capacity(self.addresses.len());
for address in &self.addresses {
addresses.push(serde_json::Value::String(address.to_string()));
}
```
Le wire produit reste strictement identique : tableau JSON de pubkeys en chaînes, dans l'ordre fourni par le caller.
## Helpers préparatoires et `dead_code`
`pre.002` doit installer les contrats partagés et prouver les formes wire avant que les wrappers ne soient activés. Il ne doit cependant pas injecter dans la build normale une collection de fonctions privées sans caller pendant plusieurs prereleases.
Les helpers strictement internes suivants sont donc compilés uniquement sous `#[cfg(test)]` tant qu'un wrapper réel ne les consomme pas :
- parsing interne `SolanaTransactionBinaryEncoding::from_wire` ;
- `is_empty` / `to_json_value` des configs Transaction ;
- `decode_wire` des résultats partagés ;
- helpers privés de décodage Transaction ;
- DTOs `Wire*` privés utilisés par ces décodeurs.
Cette décision ne retire **aucun type public**, getter public, enum public ou contrat public installé par `pre.002`.
Elle n'ajoute pas non plus de `#[allow(dead_code)]` : le lint reste actif et continue donc de détecter une vraie régression de code inutilisé.
À partir de `pre.003`, chaque tranche rendra disponible hors `#[cfg(test)]` uniquement le sous-ensemble de helpers effectivement utilisé par les wrappers introduits dans cette tranche. Les helpers des méthodes plus tardives restent test-only jusqu'à leur intégration effective.
## Couverture complémentaire des configs
Les deux helpers `is_empty` qui restaient inutilisés même dans la build de tests sont désormais couverts explicitement :
```text
SolanaSignatureStatusesConfig::is_empty
SolanaRequestAirdropConfig::is_empty
```
Les tests vérifient les états non vides et `Default` vide, sans modifier les contrats ni les fixtures.
## Contrats inchangés
Le correctif ne change pas :
- les 19 types publics Transactions installés par `pre.002` ;
- leurs constructeurs/getters publics ;
- `SolanaWireField<T>` et la distinction `Omitted / Null / Value` ;
- les encodings Transaction ;
- les 7 fixtures JSON ;
- le plan `010` version 3 ;
- la partition des 11 méthodes `0.2.3` ;
- les classes de sécurité `8 Read / 2 WriteSubmission / 1 Simulation` ;
- les descriptors centraux ;
- la policy no-resend ;
- les dépendances ;
- la frontière Transport -> Config/Store/Program/tracing ;
- l'absence de wrapper Transaction typed-complete avant `pre.003`.
Aucun fichier historique `pre.002.md` n'est réécrit.
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-onchain-transport-lib/src/rpc_transactions.rs
crates/ksp-onchain-transport-lib/unit_tests/rpc_transactions.rs
```
Headers Rust :
```text
rpc_transactions.rs version 1 -> 2
unit_tests/rpc_transactions.rs version 1 -> 2
```
## Fichier ajouté
```text
deltas/0.2.3/pre.002-fix.001.md
```
## Dépendances
Aucune dépendance ajoutée, supprimée ou modifiée.
La décision `pre.001` reste inchangée :
```text
base64 NON
bs58 NON
wincode NON
solana-client NON
crate RPC SDK NON
```
## Validations à rejouer
```bash
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-onchain-transport-lib
```
Résultat attendu :
```text
cargo check : aucun warning dead_code issu de rpc_transactions.rs
cargo clippy : 0 erreur et aucun warning dead_code/implicit-return issu de rpc_transactions.rs
Transport : 145 unit tests + 14 public API + 8 release completeness, 0 échec
smoke Devnet : toujours ignored sauf opt-in explicite
```
Aucune validation du fix lui-même n'est déclarée réussie sans exécution réelle sur le dépôt opérateur.
## Suite
Après validation et commit de `v0.2.3-pre.002-fix.001`, reprendre la tranche planifiée :
```text
0.2.3-pre.003
getFeeForMessage
getLatestBlockhash
getTransactionCount
isBlockhashValid
```
`pre.003` devra sortir de `#[cfg(test)]` uniquement les sérialiseurs/décodeurs réellement nécessaires à ces quatre wrappers.