diff --git a/Cargo.toml b/Cargo.toml index d9ef42c..9d1d457 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,6 +71,16 @@ pallet-grandpa = { version = "40.0.0", default-features = false } # FRAME pallets canonical Phase 1.5 — Session rotation per CHN-1 4h epochs pallet-session = { version = "40.0.1", default-features = false } +# ============================================================ +# Phase 3 — pallet-revive smart contracts per SBS-5 ratified +# (supersedes CHN-6 v1.0 → CHN-6 v2.0 PolkaVM + ink! v6 + Solidity) +# Version pin: 0.12.2 (canonical para stable2512-3, verified vía +# github.com/paritytech/polkadot-sdk/blob/polkadot-stable2512-3/substrate/frame/revive/Cargo.toml). +# Bump a 0.17.0 scope Phase 8+ (post-mainnet Substrate generation upgrade). +# ============================================================ +pallet-revive = { version = "0.12.2", default-features = false } +pallet-revive-uapi = { version = "0.13.0", default-features = false, features = ["scale"] } + # pallet-identity DEFERRED a Phase 2.7 — canonical decision empírica 2026-05-21: # pallet-identity 41.0.0 usa ReservableCurrency trait (deprecated en stable2512, # replaced por fungible::Mutate). Compat work canonical es scope Phase 2.7 cuando diff --git a/crates/origin-chain-fees/src/fee_schedule.rs b/crates/origin-chain-fees/src/fee_schedule.rs index 871ed10..5ba861f 100644 --- a/crates/origin-chain-fees/src/fee_schedule.rs +++ b/crates/origin-chain-fees/src/fee_schedule.rs @@ -99,6 +99,24 @@ pub const fn subsidy_basis_points(hw: HardwareTier, action: ChainAction) -> u32 (Ultra, Write) => 0, (Ultra, Compute) => 0, (Ultra, Transfer) => 0, + // Phase 3 SBS-5 contract actions — anti-clasista mapping interim + // (Slice 3.1 SCAFFOLD v0.0.37). Mappeo conservativo análogo a Write + // (DeployContract + UploadContractCode = storage-heavy) y Compute + // (CallContract = PolkaVM gas execution). Refinement Slice 3.2 cuando + // Mathi extiende OitFeeSchedule con measurements empíricos gas/deposit + // reales post-Phase 3 Ship 2 wired runtime Index 11. + (EmbeddedArm, DeployContract) => 9000, // Pi 5 dev contracts canonical + (EmbeddedArm, CallContract) => 10000, // educational call free + (EmbeddedArm, UploadContractCode) => 9000, // Pi 5 storage subsidy + (Portable, DeployContract) => 7500, + (Portable, CallContract) => 5000, + (Portable, UploadContractCode) => 7500, + (Desktop, DeployContract) => 2500, + (Desktop, CallContract) => 2500, + (Desktop, UploadContractCode) => 2500, + (Ultra, DeployContract) => 0, + (Ultra, CallContract) => 0, + (Ultra, UploadContractCode) => 0, } } @@ -120,7 +138,18 @@ pub const fn entitlement_unlocked(tier: SubscriptionTier, action: ChainAction) - (Pro, Write) => true, (Pro, Compute) => true, // basic compute unlocked (Pro, Transfer) => true, - // Ultra: ALL incluyendo EEG Compute (Inmutable #18) + // Phase 3 SBS-5 contract actions Free tier — conservativo interim + // (Slice 3.1 SCAFFOLD v0.0.37). Free user blocked de smart contract + // operations por default. Refinement Slice 3.2 con Mathi: educational + // world override + Pi 5 dev path canonical via subscription upgrade + // gratuita post-OCAFB ratify Phase 3 maduración. + (Free, DeployContract) => false, // requires Pro+ initial + (Free, CallContract) => false, // requires Pro+ initial + (Free, UploadContractCode) => false, // requires Pro+ initial + (Pro, DeployContract) => true, + (Pro, CallContract) => true, + (Pro, UploadContractCode) => true, + // Ultra: ALL incluyendo EEG Compute (Inmutable #18) y contracts (Ultra, _) => true, } } diff --git a/crates/origin-chain-types/src/actions.rs b/crates/origin-chain-types/src/actions.rs index df39a7e..440d795 100644 --- a/crates/origin-chain-types/src/actions.rs +++ b/crates/origin-chain-types/src/actions.rs @@ -7,9 +7,17 @@ //! análogo DCM-4 `default_material()` Marihan + `HardwareTier` mirror Phanes //! `arkhe-asset::manifest_validator`). //! -//! 4 variants canonical {Read, Write, Compute, Transfer} con ordinals 0/1/2/3 -//! stable wire format — adding variants future appende al final preservando -//! ordinals existentes (semver respect canonical). +//! 7 variants canonical {Read, Write, Compute, Transfer, DeployContract, +//! CallContract, UploadContractCode} con ordinals 0..=6 stable wire format — +//! adding variants future appende al final preservando ordinals existentes +//! (semver respect canonical). +//! +//! **Phase 3 SBS-5 ratified extension (2026-05-27, Slice 3.1 Ship 1 v0.0.37)**: +//! variants 4/5/6 añadidas para `pallet-revive` smart contract operations +//! (PolkaVM RISC-V + ink! v6 + Solidity). Permiten fee differentiation futura +//! por Mathi `OitFeeSchedule` post-OCAFB ratify — `DeployContract` gas-heavy +//! (storage deposit + bytecode upload), `CallContract` execution-metered, +//! `UploadContractCode` storage-only (sin instantiate). //! //! Bidirectional conversion canonical wire-compat via `From` + serde //! `rename_all="snake_case"` + scale-codec derives canonical. @@ -45,6 +53,20 @@ pub enum ChainAction { /// Transfer OGC token (peer-to-peer, fee redirect, governance staking). /// Free user limitado a basic ≤10 OGC/day per OCAFB-4 entitlement. Transfer, + /// Deploy smart contract via `pallet-revive::instantiate` (Phase 3 SBS-5). + /// Carga code hash + storage deposit + instantiate constructor. Fee + /// modulado por storage deposit (DepositPerByte + DepositPerItem) + gas + /// metering PolkaVM execution. + DeployContract, + /// Invoke smart contract method via `pallet-revive::call` (Phase 3 SBS-5). + /// Fee gas-metered en PolkaVM execution + storage write deposits si la + /// llamada muta state. Read-only views también incurren minimal gas fee. + CallContract, + /// Upload PolkaVM bytecode WITHOUT instantiate via `pallet-revive::upload_code` + /// (Phase 3 SBS-5). Solo deposita code en storage por su size + lockup + /// deposit canonical. Permite separar code authorship de instantiation + /// (e.g. template publishing pattern por Sussi Ethos credentials Slice 3.4). + UploadContractCode, } impl ChainAction { @@ -55,18 +77,25 @@ impl ChainAction { ChainAction::Write => "write", ChainAction::Compute => "compute", ChainAction::Transfer => "transfer", + ChainAction::DeployContract => "deploy_contract", + ChainAction::CallContract => "call_contract", + ChainAction::UploadContractCode => "upload_contract_code", } } /// Ordinal canonical para wire format `u8` (cross-crate sin requerir serde). - /// `0=Read 1=Write 2=Compute 3=Transfer`. Stable order — adding variants - /// future appende al final preservando ordinals existentes (semver respect). + /// `0=Read 1=Write 2=Compute 3=Transfer 4=DeployContract 5=CallContract + /// 6=UploadContractCode`. Stable order — adding variants future appende al + /// final preservando ordinals existentes (semver respect). pub const fn ordinal(self) -> u8 { match self { ChainAction::Read => 0, ChainAction::Write => 1, ChainAction::Compute => 2, ChainAction::Transfer => 3, + ChainAction::DeployContract => 4, + ChainAction::CallContract => 5, + ChainAction::UploadContractCode => 6, } } @@ -78,6 +107,9 @@ impl ChainAction { 1 => Some(ChainAction::Write), 2 => Some(ChainAction::Compute), 3 => Some(ChainAction::Transfer), + 4 => Some(ChainAction::DeployContract), + 5 => Some(ChainAction::CallContract), + 6 => Some(ChainAction::UploadContractCode), _ => None, } } @@ -87,38 +119,58 @@ impl ChainAction { mod tests { use super::*; + /// All 7 canonical variants para iteración exhaustiva en tests. + const ALL_VARIANTS: [ChainAction; 7] = [ + ChainAction::Read, + ChainAction::Write, + ChainAction::Compute, + ChainAction::Transfer, + ChainAction::DeployContract, + ChainAction::CallContract, + ChainAction::UploadContractCode, + ]; + #[test] fn chain_action_name_lowercase_canonical() { assert_eq!(ChainAction::Read.name(), "read"); assert_eq!(ChainAction::Write.name(), "write"); assert_eq!(ChainAction::Compute.name(), "compute"); assert_eq!(ChainAction::Transfer.name(), "transfer"); + assert_eq!(ChainAction::DeployContract.name(), "deploy_contract"); + assert_eq!(ChainAction::CallContract.name(), "call_contract"); + assert_eq!( + ChainAction::UploadContractCode.name(), + "upload_contract_code" + ); } #[test] fn chain_action_ordinal_stable_canonical() { + // Ordinals 0-3 preservados canonical (OCAFB-2 wire format estable). assert_eq!(ChainAction::Read.ordinal(), 0); assert_eq!(ChainAction::Write.ordinal(), 1); assert_eq!(ChainAction::Compute.ordinal(), 2); assert_eq!(ChainAction::Transfer.ordinal(), 3); + // Phase 3 SBS-5 extension: ordinals 4-6 appended preservando 0-3. + assert_eq!(ChainAction::DeployContract.ordinal(), 4); + assert_eq!(ChainAction::CallContract.ordinal(), 5); + assert_eq!(ChainAction::UploadContractCode.ordinal(), 6); } #[test] fn chain_action_from_ordinal_roundtrip_canonical() { - for action in [ - ChainAction::Read, - ChainAction::Write, - ChainAction::Compute, - ChainAction::Transfer, - ] { + for action in ALL_VARIANTS { assert_eq!(ChainAction::from_ordinal(action.ordinal()), Some(action)); } } #[test] fn chain_action_from_ordinal_unknown_returns_none() { - // Forward-compat: ordinals futuros NO conocidos retornan None safe - assert_eq!(ChainAction::from_ordinal(4), None); + // Forward-compat: ordinals futuros NO conocidos retornan None safe. + // Ordinal 7+ NO assigned aún (reserved para variants futuras tipo + // CrossChainBridge, PrecompileInvoke, etc). + assert_eq!(ChainAction::from_ordinal(7), None); + assert_eq!(ChainAction::from_ordinal(100), None); assert_eq!(ChainAction::from_ordinal(255), None); } @@ -128,19 +180,35 @@ mod tests { assert_eq!(json, "\"read\""); let parsed: ChainAction = serde_json::from_str("\"compute\"").unwrap(); assert_eq!(parsed, ChainAction::Compute); + // Phase 3 SBS-5 variants también serializan snake_case. + assert_eq!( + serde_json::to_string(&ChainAction::DeployContract).unwrap(), + "\"deploy_contract\"" + ); + let parsed_upload: ChainAction = + serde_json::from_str("\"upload_contract_code\"").unwrap(); + assert_eq!(parsed_upload, ChainAction::UploadContractCode); } #[test] fn chain_action_scale_encode_decode_roundtrip_canonical() { - for action in [ - ChainAction::Read, - ChainAction::Write, - ChainAction::Compute, - ChainAction::Transfer, - ] { + for action in ALL_VARIANTS { let encoded = action.encode(); let decoded = ChainAction::decode(&mut &encoded[..]).unwrap(); assert_eq!(decoded, action); } } + + #[test] + fn chain_action_phase_3_variants_distinct_from_legacy() { + // Phase 3 contract operations NO se solapan con generic Read/Write/Compute. + // Diseñadas para fee differentiation post-Mathi OitFeeSchedule extensión + // canonical post-OCAFB ratify Phase 3.x maduración. + assert_ne!(ChainAction::DeployContract, ChainAction::Write); + assert_ne!(ChainAction::CallContract, ChainAction::Compute); + assert_ne!(ChainAction::UploadContractCode, ChainAction::Write); + // Pero ChainAction::DeployContract es semánticamente "Write con bytecode" — + // Mathi puede mapear DeployContract → Write fee_for_action() base + // multiplicado por bytecode size (canonical decision deferred Phase 3.2). + } }