Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: MIT OR Apache-2.0

//! ERA's canonical token policy (SoFi Amendment S11): the one network-anchored
//! policy, fixed here, held by every device by construction.
//!
//! ERA's identity is derived from these exact bytes and from nothing else:
//! [`era_policy_commit`] is `BLAKE3(DSM/policy ‖ 0x00 ‖ TokenPolicyV3 bytes)`,
//! the commitment every token has (§47). A different byte is a different
//! ERA — every ERA balance key and the reserve id move with it — so the value
//! is pinned by this module's tests and by the specification's check value.

use std::sync::LazyLock;

use crate::economic::token_policy::{parse_token_policy, TokenPolicy};
use crate::types::error::DsmError;

/// ERA's `TokenPolicyV3` bytes, field by field (SoFi §47, Amendment S11).
const ERA_POLICY_PROTO: [u8; 40] = [
// TokenPolicyV3 { policy_bytes (field 1) }: the 38-byte blob.
0x0A, 0x26, //
// version 3, fungible, native.
0x03, 0x00, 0x00, //
// flags: burn | transferable; no recipient allowlist.
0x03, //
// release rule: the beta faucet. Network-anchored, so no creator and no
// signer set follow (Amendment S11).
0x01, //
// ticker "ERA".
0x03, b'E', b'R', b'A', //
// alias "ERA".
0x00, 0x03, b'E', b'R', b'A', //
// decimals: whole ERA.
0x00, //
// genesis supply: 80,000,000,000 (u128, big-endian; owner, 2026-09-26).
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, 0x12, 0xA0, 0x5F, 0x20, 0x00, //
// description: none; icon: none.
0x00, 0x00, 0x00, 0x00, //
// recipient allowlist: none (kind NONE, count 0).
0x00, 0x00, 0x00,
];

static ERA_POLICY_COMMIT: LazyLock<[u8; 32]> = LazyLock::new(|| {
crate::core::token::policy::TokenPolicySystem::commitment_of(&ERA_POLICY_PROTO)
});

static ERA_POLICY: LazyLock<Result<TokenPolicy, String>> =
LazyLock::new(|| parse_token_policy(&ERA_POLICY_PROTO));

/// ERA's policy bytes, exactly as committed.
pub fn era_policy_bytes() -> &'static [u8] {
&ERA_POLICY_PROTO
}

/// ERA's policy commitment, derived from its bytes.
pub fn era_policy_commit() -> [u8; 32] {
*ERA_POLICY_COMMIT
}

/// ERA's policy, read by the one parser. The compiled bytes parse — this
/// module's tests pin it — and Core does not panic on its own data, so a
/// failure reaches the caller as an error.
pub fn era_policy() -> Result<&'static TokenPolicy, DsmError> {
ERA_POLICY.as_ref().map_err(|e| {
DsmError::invalid_operation(format!("ERA's compiled policy does not parse: {e}"))
})
}

#[cfg(test)]
mod tests {
use super::*;
use crate::core::token::policy::TokenPolicySystem;
use crate::economic::token_policy::Release;
use prost::Message;

/// SoFi Amendment S11: ERA's commitment is derived from its bytes, and it
/// is the check value the specification states. Any byte changed is a
/// different ERA, and it goes red here.
#[test]
fn eras_commitment_is_derived_from_its_bytes_and_is_the_specifications() {
assert_eq!(
era_policy_commit(),
TokenPolicySystem::commitment_of(era_policy_bytes())
);
assert_eq!(
crate::utils::text_id::encode_base32_crockford(&era_policy_commit()),
"JXPMPGJH45HDTE0ARWE2CTB9E9BWTQZ3T78CE5RFF1RXMR9VKK80"
);
}

/// One policy, one commitment: ERA's bytes are the canonical encoding of
/// the blob they carry.
#[test]
fn eras_bytes_are_the_canonical_encoding_of_its_blob() {
let decoded = crate::types::proto::TokenPolicyV3::decode(era_policy_bytes())
.expect("ERA's wrapper decodes");
assert_eq!(decoded.encode_to_vec(), era_policy_bytes());
}

/// Every field of ERA's policy, as SoFi Amendment S11 fixes it.
#[test]
fn eras_policy_states_what_the_specification_fixes() {
assert_eq!(
era_policy().expect("ERA's policy parses"),
&TokenPolicy {
ticker: "ERA".into(),
alias: "ERA".into(),
decimals: 0,
genesis_supply: 80_000_000_000,
release: Release::Faucet,
description: None,
icon_url: None,
burn_enabled: true,
transferable: true,
allowlist_device_ids: Vec::new(),
}
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

//! src/core/token/mod.rs

pub mod era_policy;
pub mod policy;
pub mod token_state_manager;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
//! any other way: registration takes the bytes, recomputes the commitment
//! from them and derives the enforcer's view from what the blob says
//! (`policy_enforcement::enforced_policy`), and enforcement is keyed by the
//! commitment an operation names. A token with no committed policy — ERA,
//! whose `policy_commit` constant has no preimage yet — has no policy here,
//! and an operation naming it is refused for that reason.
//! commitment an operation names. ERA's policy is pre-rooted: every device
//! holds its bytes by construction ([`crate::core::token::era_policy`], SoFi
//! Amendment S11), so it is answered from them, never from the cache, which
//! could evict it, nor from the durable store, which never holds it. A
//! commitment no committed policy is in hand for permits nothing.
//!
//! Determinism rules: no wall-clock; enforcement reads only what the
//! operation carries and what Core derived from canonical state.
Expand Down Expand Up @@ -98,6 +100,17 @@ impl TokenPolicySystem {
"token policy: the committed bytes do not parse: {e}"
))
})?;
// Exactly one network-anchored policy exists, ERA's, and it is fixed
// in Core; no other is registered (SoFi Amendment S11).
if !matches!(
parsed.release,
crate::economic::token_policy::Release::AllAtCreation { .. }
) {
return Err(DsmError::invalid_operation(
"token policy: a network-anchored policy is registered by nobody — the one \
that exists, ERA's, is fixed in Core (Amendment S11)",
));
}
let anchor = PolicyAnchor::from_bytes(commit);
self.policy_cache.store_policy(
anchor.clone(),
Expand All @@ -113,6 +126,13 @@ impl TokenPolicySystem {
/// commitment, but there is nothing to evaluate against either way.
pub async fn policy_at(&self, commit: &[u8; 32]) -> Result<Option<TokenPolicy>, DsmError> {
let anchor = PolicyAnchor::from_bytes(*commit);
if *commit == crate::core::token::era_policy::era_policy_commit() {
let era = crate::core::token::era_policy::era_policy()?;
return Ok(Some(TokenPolicy::new_with_anchor(
enforced_policy(era),
anchor,
)));
}
if let Some(policy) = self.policy_cache.get_policy(&anchor).await? {
return Ok(Some(policy));
}
Expand Down Expand Up @@ -243,15 +263,15 @@ mod tests {
);
}

/// A commitment no committed policy is in hand for — ERA's today, whose
/// constant has no preimage — permits nothing: the operation is denied
/// for the absence, never allowed by a default.
/// A commitment no committed policy is in hand for permits nothing: the
/// operation is denied for the absence, never allowed by a default.
#[tokio::test]
async fn an_operation_naming_a_commitment_without_a_policy_is_denied() {
let system = TokenPolicySystem::new();
let era = crate::core::token::token_state_manager::era_policy_commit();
let unregistered =
TokenPolicySystem::commitment_of(&token_policy_bytes_with(9, POLICY_FLAG_TRANSFERABLE));
let result = system
.enforce_policy(&era, "transfer", &context(1))
.enforce_policy(&unregistered, "transfer", &context(1))
.await
.expect("enforced");
assert!(!result.allowed);
Expand All @@ -261,6 +281,46 @@ mod tests {
);
}

/// SoFi Amendment S11: ERA's policy is pre-rooted. A transfer and a burn of
/// ERA are permitted by ERA's own committed rules with nothing registered,
/// nothing cached, and a resolver that is never asked.
#[tokio::test]
async fn eras_policy_is_answered_from_cores_bytes_never_cached_or_resolved() {
use std::sync::atomic::{AtomicUsize, Ordering};
let system = TokenPolicySystem::new();
let asked = Arc::new(AtomicUsize::new(0));
let counter = asked.clone();
system.set_policy_resolver(Arc::new(move |_commit: &[u8; 32]| {
counter.fetch_add(1, Ordering::SeqCst);
None
}));
let era = crate::core::token::era_policy::era_policy_commit();
for operation in ["transfer", "burn"] {
let result = system
.enforce_policy(&era, operation, &context(1))
.await
.expect("enforced");
assert!(result.allowed, "ERA {operation}: {}", result.reason);
}
assert_eq!(
asked.load(Ordering::SeqCst),
0,
"the resolver was asked for ERA"
);
assert!(system.policy_cache.is_empty(), "ERA took a cache slot");
}

/// ERA's own bytes are network-anchored, so they are registered by nobody:
/// its policy is Core's, fixed (Amendment S11).
#[tokio::test]
async fn eras_own_bytes_are_never_registered() {
let system = TokenPolicySystem::new();
assert!(system
.register_policy(crate::core::token::era_policy::era_policy_bytes())
.is_err());
assert!(system.policy_cache.is_empty());
}

/// The durable bytes at a commitment are taken on a cache miss when they
/// re-hash to it; the policy is then the one those bytes commit.
#[tokio::test]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,24 +260,25 @@ impl PolicyEnforcer {
#[cfg(test)]
mod tests {
use super::*;
use crate::economic::token_policy::{ReleaseRule, TokenPolicy as ParsedTokenPolicy};
use crate::economic::token_policy::{Release, TokenPolicy as ParsedTokenPolicy};
use crate::types::policy_types::{PolicyAnchor, PolicyCondition, PolicyFile, TokenPolicy};

fn fungible_fixture() -> ParsedTokenPolicy {
ParsedTokenPolicy {
creator_genesis: [0x31; 32],
creator_device_id: [0x32; 32],
ticker: "DSM".into(),
alias: "DSM Token".into(),
decimals: 8,
genesis_supply: 1_000_000,
release_rule: ReleaseRule::AllAtCreation,
release: Release::AllAtCreation {
creator_genesis: [0x31; 32],
creator_device_id: [0x32; 32],
threshold: 1,
signers: vec![vec![0xAB; 64]],
},
description: Some("A test token".into()),
icon_url: Some("dsm:icon".into()),
burn_enabled: true,
transferable: true,
threshold: 1,
signers: vec![vec![0xAB; 64]],
allowlist_device_ids: Vec::new(),
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

//! Token policy commits and canonical balance keys.
//!
//! A token is named in hashing by its 32-byte CPTA `policy_commit`: builtins
//! (ERA, dBTC) carry fixed commits, every other token resolves through a
//! registered policy. Balance keys are derived under that commit.
//! A token is named in hashing by its 32-byte `policy_commit`: ERA's is
//! derived from its committed policy ([`crate::core::token::era_policy`]),
//! dBTC's is fixed, and every other token resolves through a registered
//! policy. Balance keys are derived under that commit.

use std::collections::HashMap;

Expand Down Expand Up @@ -91,23 +92,16 @@ pub fn derive_canonical_balance_key(
format!("{prefix}|{token_id}")
}

/// ERA's policy commitment, derived from ERA's committed policy bytes (SoFi
/// Amendment S11). Callers that mean ERA use this and carry no
/// string-keyed lookup.
pub use crate::core::token::era_policy::era_policy_commit;

/// Deterministic policy_commit lookup for builtin token types.
/// Used by state machine core to apply token operations deterministically.
/// The builtin ERA policy commit, infallibly.
///
/// `builtin_policy_commit_for_token("ERA")` returns `Option` only because it
/// is a string-keyed lookup; the "ERA" arm is a constant that cannot miss.
/// Callers that mean ERA specifically should use this and carry no
/// panic-or-error path for an impossibility.
pub fn era_policy_commit() -> [u8; 32] {
ERA_POLICY_COMMIT
}

pub fn builtin_policy_commit_for_token(token_id: &str) -> Option<[u8; 32]> {
// These values must match the SDK's policy/builtins.rs for consistency.
// Era/dBTC are the canonical builtin tokens for DSM.
match token_id {
"ERA" => Some(ERA_POLICY_COMMIT),
"ERA" => Some(era_policy_commit()),
"dBTC" => Some(DBTC_POLICY_COMMIT),
_ => None,
}
Expand All @@ -123,7 +117,7 @@ pub fn builtin_policy_commit_for_token(token_id: &str) -> Option<[u8; 32]> {
/// balance keys in the canonical `{prefix}|{token_id}` format produced by
/// [`derive_canonical_balance_key`].
pub fn builtin_token_id_for_policy_commit(policy_commit: &[u8; 32]) -> Option<&'static str> {
if *policy_commit == ERA_POLICY_COMMIT {
if *policy_commit == era_policy_commit() {
Some("ERA")
} else if *policy_commit == DBTC_POLICY_COMMIT {
Some("dBTC")
Expand All @@ -132,23 +126,18 @@ pub fn builtin_token_id_for_policy_commit(policy_commit: &[u8; 32]) -> Option<&'
}
}

const ERA_POLICY_COMMIT: [u8; 32] = [
0xaf, 0x13, 0x49, 0xb9, 0xf5, 0xf9, 0xa1, 0xa6, 0xa0, 0x40, 0x4d, 0xea, 0x36, 0xdc, 0xc9, 0x49,
0x9b, 0xcb, 0x25, 0xc9, 0xad, 0xc1, 0x12, 0xb7, 0xcc, 0x9a, 0x93, 0xca, 0xe4, 0x1f, 0x32, 0x62,
];

const DBTC_POLICY_COMMIT: [u8; 32] = [
0x03, 0xa4, 0x2b, 0x67, 0x19, 0x17, 0xaf, 0x84, 0x2f, 0x07, 0x3d, 0x87, 0xcf, 0xa4, 0x59, 0xd8,
0x45, 0xb9, 0x68, 0xfd, 0xb1, 0xab, 0xcb, 0x03, 0x31, 0x2d, 0x91, 0x4e, 0x35, 0x01, 0x62, 0x22,
];

/// Resolve policy_commit for a token by ticker.
///
/// §9.1: all TokenOps MUST include `policy_commit`. Builtins (ERA, dBTC)
/// resolve to their precomputed constants. For CPTA-anchored custom tokens
/// the canonical policy_commit is `BLAKE3-256("DSM/cpta\0" || canonical_cpta_bytes)`
/// and can only be produced by reading the registered `TokenPolicyV3` — not
/// derived from the ticker string.
/// §9.1: all TokenOps MUST include `policy_commit`. Builtins resolve to
/// theirs: ERA's derived from its committed policy, dBTC's fixed. For every
/// other token the canonical policy_commit is
/// `BLAKE3(DSM/policy ‖ 0x00 ‖ TokenPolicyV3 bytes)` and can only be produced
/// by reading the registered policy — not derived from the ticker string.
///
/// This function therefore strict-fails for any non-builtin token. Callers
/// that handle custom tokens MUST carry `policy_commit` explicitly on the
Expand Down
Loading
Loading