0.4.7-pre.002

This commit is contained in:
2026-08-02 01:26:37 +02:00
parent aa3f58db30
commit 7a50ddd531
8 changed files with 5454 additions and 169 deletions

View File

@@ -18,7 +18,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.4.7" version = "0.4.7-pre.2"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-bot3" repository = "https://git.sasedev.com/Sasedev/khadhroony-bot3"

View File

@@ -0,0 +1,129 @@
<!-- file: docs/validation/V0_4_7_PRE_002_METAPLEX_COVERAGE_AND_FACT_OWNERSHIP.md -->
<!-- version: 1 -->
# `0.4.7-pre.002` — couverture Metaplex et propriété des faits
## Résultat
Le contrat couvre les 58 discriminants `0..57` sans trou ni statut ambigu. La source des comptes et signers est lIDL Metaplex archivée `V1_14_0`; la constructibilité courante est confrontée à linventaire `mpl-token-metadata 5.1.1`.
### Classification fermée
- `decode_only_bridge_boundary` : 1 instruction(s) ;
- `decode_only_obsolete` : 7 instruction(s) ;
- `decode_only_replaced` : 9 instruction(s) ;
- `executable_current` : 41 instruction(s) ;
Les opérations `executable_current` doivent toutes posséder un intent et un builder à la fin de `pre.006`. Les statuts decode-only ne peuvent être utilisés que pour une frontière Bubblegum, une opération obsolète ou une opération remplacée par une variante canonique.
## Contrat par instruction
| Disc. | Instruction | Statut | Exécution | Cible | Signers IDL |
|------:|-------------------------------------------------------------|-------------------------------|------------------------------------|-----------|---------------------------------------------------------------------------------------------------------|
| 0 | `CreateMetadataAccount` | `decode_only_replaced` | `forbidden_replaced` | `—` | mintAuthority, payer |
| 1 | `UpdateMetadataAccount` | `decode_only_replaced` | `forbidden_replaced` | `—` | updateAuthority |
| 2 | `DeprecatedCreateMasterEdition` | `decode_only_obsolete` | `forbidden_obsolete` | `—` | updateAuthority, printingMintAuthority, mintAuthority, payer, oneTimePrintingAuthorizationMintAuthority |
| 3 | `DeprecatedMintNewEditionFromMasterEditionViaPrintingToken` | `decode_only_obsolete` | `forbidden_obsolete` | `—` | mintAuthority, burnAuthority, payer |
| 4 | `UpdatePrimarySaleHappenedViaToken` | `executable_current` | `required` | `pre.004` | owner |
| 5 | `DeprecatedSetReservationList` | `decode_only_obsolete` | `forbidden_obsolete` | `—` | resource |
| 6 | `DeprecatedCreateReservationList` | `decode_only_obsolete` | `forbidden_obsolete` | `—` | payer, updateAuthority |
| 7 | `SignMetadata` | `executable_current` | `required` | `pre.004` | creator |
| 8 | `DeprecatedMintPrintingTokensViaToken` | `decode_only_obsolete` | `forbidden_obsolete` | `—` | burnAuthority |
| 9 | `DeprecatedMintPrintingTokens` | `decode_only_obsolete` | `forbidden_obsolete` | `—` | updateAuthority |
| 10 | `CreateMasterEdition` | `decode_only_replaced` | `forbidden_replaced` | `—` | updateAuthority, mintAuthority, payer |
| 11 | `MintNewEditionFromMasterEditionViaToken` | `executable_current` | `required` | `pre.006` | newMintAuthority, payer, tokenAccountOwner |
| 12 | `ConvertMasterEditionV1ToV2` | `decode_only_replaced` | `forbidden_replaced` | `—` | aucun |
| 13 | `MintNewEditionFromMasterEditionViaVaultProxy` | `decode_only_obsolete` | `forbidden_obsolete` | `—` | newMintAuthority, payer, vaultAuthority |
| 14 | `PuffMetadata` | `executable_current` | `required` | `pre.003` | aucun |
| 15 | `UpdateMetadataAccountV2` | `executable_current` | `required` | `pre.003` | updateAuthority |
| 16 | `CreateMetadataAccountV2` | `decode_only_replaced` | `forbidden_replaced` | `—` | mintAuthority, payer |
| 17 | `CreateMasterEditionV3` | `executable_current` | `required` | `pre.006` | updateAuthority, mintAuthority, payer |
| 18 | `VerifyCollection` | `executable_current` | `required` | `pre.004` | collectionAuthority, payer |
| 19 | `Utilize` | `executable_current` | `required` | `pre.006` | useAuthority |
| 20 | `ApproveUseAuthority` | `executable_current` | `required` | `pre.006` | owner, payer |
| 21 | `RevokeUseAuthority` | `executable_current` | `required` | `pre.006` | owner |
| 22 | `UnverifyCollection` | `executable_current` | `required` | `pre.004` | collectionAuthority |
| 23 | `ApproveCollectionAuthority` | `executable_current` | `required` | `pre.004` | updateAuthority, payer |
| 24 | `RevokeCollectionAuthority` | `executable_current` | `required` | `pre.004` | revokeAuthority |
| 25 | `SetAndVerifyCollection` | `executable_current` | `required` | `pre.004` | collectionAuthority, payer |
| 26 | `FreezeDelegatedAccount` | `decode_only_replaced` | `forbidden_replaced` | `—` | delegate |
| 27 | `ThawDelegatedAccount` | `decode_only_replaced` | `forbidden_replaced` | `—` | delegate |
| 28 | `RemoveCreatorVerification` | `executable_current` | `required` | `pre.004` | creator |
| 29 | `BurnNft` | `decode_only_replaced` | `forbidden_replaced` | `—` | owner |
| 30 | `VerifySizedCollectionItem` | `executable_current` | `required` | `pre.004` | collectionAuthority, payer |
| 31 | `UnverifySizedCollectionItem` | `executable_current` | `required` | `pre.004` | collectionAuthority, payer |
| 32 | `SetAndVerifySizedCollectionItem` | `executable_current` | `required` | `pre.004` | collectionAuthority, payer |
| 33 | `CreateMetadataAccountV3` | `executable_current` | `required` | `pre.003` | mintAuthority, payer |
| 34 | `SetCollectionSize` | `executable_current` | `required` | `pre.004` | collectionAuthority |
| 35 | `SetTokenStandard` | `executable_current` | `required` | `pre.003` | updateAuthority |
| 36 | `BubblegumSetCollectionSize` | `decode_only_bridge_boundary` | `forbidden_cross_program_boundary` | `—` | collectionAuthority, bubblegumSigner |
| 37 | `BurnEditionNft` | `decode_only_replaced` | `forbidden_replaced` | `—` | owner |
| 38 | `CreateEscrowAccount` | `executable_current` | `required` | `pre.006` | payer, authority |
| 39 | `CloseEscrowAccount` | `executable_current` | `required` | `pre.006` | payer |
| 40 | `TransferOutOfEscrow` | `executable_current` | `required` | `pre.006` | payer, authority |
| 41 | `Burn` | `executable_current` | `required` | `pre.005` | authority |
| 42 | `Create` | `executable_current` | `required` | `pre.003` | authority, payer |
| 43 | `Mint` | `executable_current` | `required` | `pre.006` | authority, payer |
| 44 | `Delegate` | `executable_current` | `required` | `pre.005` | authority, payer |
| 45 | `Revoke` | `executable_current` | `required` | `pre.005` | authority, payer |
| 46 | `Lock` | `executable_current` | `required` | `pre.005` | authority, payer |
| 47 | `Unlock` | `executable_current` | `required` | `pre.005` | authority, payer |
| 48 | `Migrate` | `executable_current` | `required` | `pre.006` | payer, authority |
| 49 | `Transfer` | `executable_current` | `required` | `pre.005` | authority, payer |
| 50 | `Update` | `executable_current` | `required` | `pre.003` | authority, payer |
| 51 | `Use` | `executable_current` | `required` | `pre.006` | authority, payer |
| 52 | `Verify` | `executable_current` | `required` | `pre.004` | authority |
| 53 | `Unverify` | `executable_current` | `required` | `pre.004` | authority |
| 54 | `Collect` | `executable_current` | `required` | `pre.006` | authority |
| 55 | `Print` | `executable_current` | `required` | `pre.006` | editionMintAuthority, payer |
| 56 | `Resize` | `executable_current` | `required` | `pre.006` | authority |
| 57 | `CloseAccounts` | `executable_current` | `required` | `pre.005` | authority |
Les listes positionnelles complètes des comptes, leurs flags writable/signer, les préflights et les postconditions sont conservés sous `instruction_execution_contract` dans la matrice JSON active.
## Propriété unique des faits
| Fait | Propriétaire | Source autoritative |
|--------------------------------------|-------------------|---------------------------------------------------|
| `metadata.name` | `metadata` | `MetadataV1.name` |
| `metadata.symbol` | `metadata` | `MetadataV1.symbol` |
| `metadata.uri` | `metadata` | `MetadataV1.uri` |
| `metadata.seller_fee_basis_points` | `metadata` | `MetadataV1.seller_fee_basis_points` |
| `metadata.creators` | `metadata` | `MetadataV1.data.creators` |
| `metadata.collection` | `metadata` | `MetadataV1.collection` |
| `metadata.uses` | `metadata` | `MetadataV1.uses` |
| `metadata.token_standard` | `metadata` | `MetadataV1.token_standard` |
| `metadata.is_mutable` | `metadata` | `MetadataV1.is_mutable` |
| `metadata.programmable_config` | `metadata` | `MetadataV1.programmable_config` |
| `admin.update_authority` | `admin` | `MetadataV1.update_authority` |
| `admin.collection_authority` | `admin` | `CollectionAuthorityRecord` |
| `admin.use_authority` | `admin` | `UseAuthorityRecord` |
| `admin.delegate` | `admin` | `MetadataDelegate/HolderDelegate/TokenRecord` |
| `lifecycle.primary_sale_happened` | `lifecycle` | `MetadataV1.primary_sale_happened` |
| `lifecycle.edition` | `lifecycle` | `Edition/MasterEdition` |
| `lifecycle.edition_marker` | `lifecycle` | `EditionMarker/EditionMarkerV2` |
| `lifecycle.programmable_token_state` | `lifecycle` | `TokenRecord.state` |
| `lifecycle.locked_transfer` | `lifecycle` | `TokenRecord.locked_transfer` |
| `lifecycle.token_owned_escrow` | `lifecycle` | `TokenOwnedEscrow` |
| `lifecycle.historical_reservation` | `lifecycle` | `ReservationListV1/V2` |
| `risk.royalty_configuration` | `risk_compliance` | `seller_fee_basis_points fact reference` |
| `risk.unverified_creators` | `risk_compliance` | `creators verification fact reference` |
| `risk.mutable_metadata` | `risk_compliance` | `is_mutable fact reference` |
| `risk.rule_set` | `risk_compliance` | `programmable_config rule_set fact reference` |
| `risk.sensitive_delegation` | `risk_compliance` | `delegate records and TokenRecord fact reference` |
Les observations dinstruction décrivent uniquement les mutations. Les snapshots de comptes possédés restent autoritatifs pour létat final et ne doivent jamais être dupliqués par une projection concurrente.
## Non-fusion Token-2022
- domaine Metaplex : `metaplex_token_metadata` ;
- domaine Token-2022 : `spl_token_2022_embedded_metadata` ;
- aucune identité, URI ou autorité nest fusionnée implicitement entre les deux domaines ;
- une corrélation ultérieure doit conserver les deux provenances et signaler les conflits.
## Validation disponible dans cet environnement
- parsing JSON : effectué ;
- inventaire `0..57` : effectué ;
- unicité des faits : effectuée ;
- tests Cargo : non exécutés, car `cargo` est absent de lenvironnement de préparation.

View File

@@ -1,8 +1,16 @@
<!-- file: kb-lib/CHANGELOG.md --> <!-- file: kb-lib/CHANGELOG.md -->
<!-- version: 6 --> <!-- version: 7 -->
# CHANGELOG — kb-lib # CHANGELOG — kb-lib
## 0.4.7-pre.002 — contrat Metaplex fermé
- classification exhaustive des 58 discriminants Metaplex Token Metadata ;
- comptes positionnels et signers importés de lIDL archivée `V1_14_0` ;
- propriété unique des faits metadata, admin, lifecycle et risk/compliance ;
- exposition directe des créateurs, collection, uses, token standard et programmable config dans le snapshot metadata autoritatif ;
- tests contractuels dexhaustivité, dunicité et de séparation avec Token-2022.
## 0.4.6 ## 0.4.6
- alignement de la crate sur la version fonctionnelle bot3 `0.4.6` ; - alignement de la crate sur la version fonctionnelle bot3 `0.4.6` ;

View File

@@ -1,5 +1,5 @@
<!-- file: kb-lib/README.md --> <!-- file: kb-lib/README.md -->
<!-- version: 14 --> <!-- version: 15 -->
# kb-lib # kb-lib
@@ -56,7 +56,7 @@ Les matrices actives sont maintenues sous [`../test-fixtures/contract-matrices/`
## Statut et limites ## Statut et limites
La migration des surfaces historiques jusquà un périmètre proche de bot2 `0.4.6` est largement réalisée. Le décodeur et les matérialisations Metaplex Token Metadata sont présents, mais lexécuteur et la clôture fonctionnelle de cette surface restent à réaliser. Le registre ElGamal nest pas déclaré validé sur Devnet ou Mainnet. La migration des surfaces historiques jusquà un périmètre proche de bot2 `0.4.6` est largement réalisée. Le décodeur Metaplex Token Metadata couvre les 58 discriminants et les matérialisations possèdent désormais un contrat fermé de propriété des faits. Lexécuteur et la clôture fonctionnelle de cette surface restent à réaliser entre `0.4.7-pre.003` et `pre.006`. Le registre ElGamal nest pas déclaré validé sur Devnet ou Mainnet.
## Documents ## Documents

View File

@@ -1,12 +1,12 @@
<!-- file: kb-lib/TODO.md --> <!-- file: kb-lib/TODO.md -->
<!-- version: 3 --> <!-- version: 4 -->
# TODO — kb-lib # TODO — kb-lib
## Entre `0.4.6` et `0.4.7` — Metaplex Token Metadata ## Entre `0.4.6` et `0.4.7` — Metaplex Token Metadata
- [ ] Matérialisation - vérifier la couverture exacte migrée depuis bot2. - [x] Matérialisation - vérifier la couverture exacte migrée depuis bot2.
- [ ] Matérialisation - compléter uniquement les projections réellement manquantes. - [x] Matérialisation - compléter uniquement les projections réellement manquantes et fermer leur propriété unique.
- [ ] Exécution - implémenter lexécuteur à partir des contrats officiels vérifiés. - [ ] Exécution - implémenter lexécuteur à partir des contrats officiels vérifiés.
- [ ] Tests - ajouter les tests unitaires et contractuels de lexécuteur. - [ ] Tests - ajouter les tests unitaires et contractuels de lexécuteur.
- [ ] Intégration - préparer les contrats nécessaires au pipeline et aux démonstrations. - [ ] Intégration - préparer les contrats nécessaires au pipeline et aux démonstrations.

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/decoder/metadata/metaplex_token_metadata/decoder.rs // file: kb-lib/src/decoder/metadata/metaplex_token_metadata/decoder.rs
// version: 30 // version: 31
//! Exact contextual dispatch for Metaplex Token Metadata. //! Exact contextual dispatch for Metaplex Token Metadata.
@@ -3051,6 +3051,80 @@ mod tests {
} }
} }
#[test]
fn pre_002_contract_closes_all_instruction_statuses_and_idl_accounts() {
let matrix: serde_json::Value = match serde_json::from_str(AUDIT_MATRIX_JSON) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("matrix parse failed: {error}"),
};
let entries = match matrix
.get("instruction_execution_contract")
.and_then(serde_json::Value::as_array)
{
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("instruction_execution_contract missing"),
};
assert_eq!(entries.len(), 58);
let mut discriminators = std::collections::BTreeSet::new();
for entry in entries {
let discriminator = match entry
.get("discriminator")
.and_then(serde_json::Value::as_u64)
{
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("instruction discriminator missing"),
};
assert!(discriminators.insert(discriminator));
assert!(entry.get("accounts").is_some_and(serde_json::Value::is_array));
assert!(entry.get("signers").is_some_and(serde_json::Value::is_array));
assert!(entry
.get("coverage_status")
.and_then(serde_json::Value::as_str)
.is_some_and(|value| return value != "pending" && !value.is_empty()));
assert!(entry
.get("execute")
.and_then(serde_json::Value::as_str)
.is_some_and(|value| return value != "pending" && !value.is_empty()));
}
assert_eq!(discriminators, (0u64..58u64).collect());
}
#[test]
fn pre_002_fact_ownership_is_unique_and_keeps_metadata_domains_separate() {
let matrix: serde_json::Value = match serde_json::from_str(AUDIT_MATRIX_JSON) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("matrix parse failed: {error}"),
};
let facts = match matrix
.get("fact_ownership_contract")
.and_then(serde_json::Value::as_array)
{
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("fact_ownership_contract missing"),
};
let mut names = std::collections::BTreeSet::new();
for entry in facts {
let fact = match entry.get("fact").and_then(serde_json::Value::as_str) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("fact name missing"),
};
assert!(names.insert(fact));
assert!(entry
.get("owner")
.and_then(serde_json::Value::as_str)
.is_some_and(|value| return !value.is_empty()));
}
let forbidden = match matrix
.pointer("/fact_ownership_proof/cross_domain_merge_forbidden")
.and_then(serde_json::Value::as_array)
{
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("cross-domain proof missing"),
};
assert!(forbidden.contains(&serde_json::json!("metaplex_token_metadata")));
assert!(forbidden.contains(&serde_json::json!("spl_token_2022_embedded_metadata")));
}
#[test] #[test]
fn omitted_historical_discriminators_reject_invalid_wires() { fn omitted_historical_discriminators_reject_invalid_wires() {
let decoder = crate::DcMetadataMetaplexTokenMetadataDecoder; let decoder = crate::DcMetadataMetaplexTokenMetadataDecoder;

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/materializer/metadata/core.rs // file: kb-lib/src/materializer/metadata/core.rs
// version: 5 // version: 6
//! Stable Token-2022 and Metaplex metadata snapshot materializer. //! Stable Token-2022 and Metaplex metadata snapshot materializer.
@@ -134,6 +134,11 @@ pub fn materializer_metadata_materialize_metaplex_metadata_snapshot(
"symbol": metadata.symbol, "symbol": metadata.symbol,
"uri": metadata.uri, "uri": metadata.uri,
"sellerFeeBasisPoints": metadata.seller_fee_basis_points, "sellerFeeBasisPoints": metadata.seller_fee_basis_points,
"creators": metadata.payload_json.get("creators").cloned().unwrap_or(serde_json::Value::Null),
"collection": metadata.payload_json.get("collection").cloned().unwrap_or(serde_json::Value::Null),
"uses": metadata.payload_json.get("uses").cloned().unwrap_or(serde_json::Value::Null),
"tokenStandard": metadata.payload_json.get("tokenStandard").cloned().unwrap_or(serde_json::Value::Null),
"programmableConfig": metadata.payload_json.get("programmableConfig").cloned().unwrap_or(serde_json::Value::Null),
"primarySaleHappened": metadata.primary_sale_happened, "primarySaleHappened": metadata.primary_sale_happened,
"isMutable": metadata.is_mutable, "isMutable": metadata.is_mutable,
"layout": snapshot.identity.layout, "layout": snapshot.identity.layout,
@@ -218,6 +223,18 @@ mod tests_metaplex_metadata_state {
first.as_ref().map(|value| return value.payload_json["metadataSource"].clone()), first.as_ref().map(|value| return value.payload_json["metadataSource"].clone()),
std::result::Result::Ok(serde_json::json!("metaplex_owned_account")) std::result::Result::Ok(serde_json::json!("metaplex_owned_account"))
); );
assert_eq!(
first.as_ref().map(|value| return value.payload_json["creators"].clone()),
std::result::Result::Ok(
serde_json::json!([{"address":"creator111","verified":true,"share":100}])
)
);
assert_eq!(
first
.as_ref()
.map(|value| return value.payload_json["programmableConfig"].clone()),
std::result::Result::Ok(serde_json::json!({"ruleSet":"rules111"}))
);
assert_eq!( assert_eq!(
first first
.as_ref() .as_ref()