From f402776f79cf18c0d82e62e3cf4dc0b4e3b2648d Mon Sep 17 00:00:00 2001 From: trinnode Date: Thu, 26 Mar 2026 21:19:51 +0100 Subject: [PATCH] feat: Make share_decimals configurable per vault Closes #66 - Hardcoded share_decimals: 6 in Factory Ignores Asset's Actual Decimals - Add share_decimals field to CreateVaultParams and BatchVaultParams - Pass share_decimals through to InitParams instead of hardcoding 6 - Simple creation function defaults to 7u32 (SEP-41 convention) - Document relationship between asset decimals and share decimals - Common values: 6 (USDC), 7 (SEP-41), 8 (BTC), 18 (ETH) --- .../contracts/vault_factory/src/lib.rs | 43 ++++++++++++++++--- .../contracts/vault_factory/src/test.rs | 2 + .../contracts/vault_factory/src/tests.rs | 2 + .../contracts/vault_factory/src/types.rs | 14 ++++++ 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/soroban-contracts/contracts/vault_factory/src/lib.rs b/soroban-contracts/contracts/vault_factory/src/lib.rs index 470690e..68e4214 100644 --- a/soroban-contracts/contracts/vault_factory/src/lib.rs +++ b/soroban-contracts/contracts/vault_factory/src/lib.rs @@ -98,6 +98,10 @@ impl VaultFactory { // ───────────────────────────────────────────────────────────────── /// Create a minimal single-RWA vault. + /// + /// Uses a default of 7 share decimals (SEP-41 convention). For USDC-aligned + /// assets (6 decimal tokens), use `create_single_rwa_vault_full` and specify + /// `share_decimals: 6u32`. pub fn create_single_rwa_vault( e: &Env, caller: Address, @@ -119,17 +123,18 @@ impl VaultFactory { asset, name, symbol, + 7u32, // share_decimals: SEP-41 convention (7 decimals) rwa_name, rwa_symbol, rwa_document_uri, zero_str, // category 0u32, // expected_apy maturity_date, - 0u64, // funding_deadline (0 = no deadline) - 0i128, // funding_target - 0i128, // min_deposit - 0i128, // max_deposit_per_user - 200u32, // early_redemption_fee_bps (2 %) + 0u64, // funding_deadline (0 = no deadline) + 0i128, // funding_target + 0i128, // min_deposit + 0i128, // max_deposit_per_user + 200u32, // early_redemption_fee_bps (2 %) ) } @@ -137,6 +142,12 @@ impl VaultFactory { /// /// Parameters are passed as a `CreateVaultParams` struct to stay within /// Soroban's 10-argument limit per contract function. + /// + /// **Note:** `share_decimals` is required in `CreateVaultParams`. Common values: + /// - `6u32` for USDC/USDT-aligned assets + /// - `7u32` for SEP-41 convention (default recommendation) + /// - `8u32` for BTC-pegged tokens + /// - `18u32` for ETH/ERC-20 compatibility pub fn create_single_rwa_vault_full( e: &Env, caller: Address, @@ -151,6 +162,7 @@ impl VaultFactory { params.asset, params.name, params.symbol, + params.share_decimals, params.rwa_name, params.rwa_symbol, params.rwa_document_uri, @@ -169,6 +181,12 @@ impl VaultFactory { /// /// Parameters are passed as a `CreateVaultParams` struct to stay within /// Soroban's 10-argument limit per contract function. + /// + /// **Note:** `share_decimals` is required in `CreateVaultParams`. Common values: + /// - `6u32` for USDC/USDT-aligned assets + /// - `7u32` for SEP-41 convention (default recommendation) + /// - `8u32` for BTC-pegged tokens + /// - `18u32` for ETH/ERC-20 compatibility pub fn create_single_rwa_vault_batch( e: &Env, caller: Address, @@ -183,6 +201,7 @@ impl VaultFactory { params.asset, params.name, params.symbol, + params.share_decimals, params.rwa_name, params.rwa_symbol, params.rwa_document_uri, @@ -201,6 +220,9 @@ impl VaultFactory { /// /// The batch size is capped at `MAX_BATCH_SIZE` (10) to prevent gas /// exhaustion from unbounded contract deployments. + /// + /// **Note:** Each vault in the batch uses its own `share_decimals` value + /// from the corresponding `BatchVaultParams`. pub fn batch_create_vaults( e: &Env, caller: Address, @@ -222,6 +244,7 @@ impl VaultFactory { p.asset, p.name, p.symbol, + p.share_decimals, p.rwa_name, p.rwa_symbol, p.rwa_document_uri, @@ -511,6 +534,7 @@ impl VaultFactory { asset: Address, name: String, symbol: String, + share_decimals: u32, rwa_name: String, rwa_symbol: String, rwa_document_uri: String, @@ -571,11 +595,18 @@ impl VaultFactory { // Build the InitParams struct for the vault constructor. // Using a struct keeps us under Soroban's 10-arg limit per function. + // + // Note: share_decimals is passed from the caller. The relationship between + // share_decimals and asset_decimals affects the share/asset ratio calculation: + // - For USDC-aligned assets (6 decimals): use share_decimals = 6 + // - For SEP-41 convention (7 decimals): use share_decimals = 7 + // - For BTC-pegged tokens (8 decimals): use share_decimals = 8 + // - For ETH/ERC-20 compatibility (18 decimals): use share_decimals = 18 let init_params = SingleRwaVaultInitParams { asset: vault_asset.clone(), share_name: name.clone(), share_symbol: symbol.clone(), - share_decimals: 6u32, // USDC convention + share_decimals, admin: admin.clone(), zkme_verifier: zkme.clone(), cooperator: coop.clone(), diff --git a/soroban-contracts/contracts/vault_factory/src/test.rs b/soroban-contracts/contracts/vault_factory/src/test.rs index 428c49c..4b404c7 100644 --- a/soroban-contracts/contracts/vault_factory/src/test.rs +++ b/soroban-contracts/contracts/vault_factory/src/test.rs @@ -58,6 +58,7 @@ fn test_create_single_rwa_vault_full() { asset: asset.clone(), name: String::from_str(&e, "Full Vault"), symbol: String::from_str(&e, "FV"), + share_decimals: 7u32, // SEP-41 convention rwa_name: String::from_str(&e, "Private Credit"), rwa_symbol: String::from_str(&e, "PC"), rwa_document_uri: String::from_str(&e, "https://doc.com"), @@ -93,6 +94,7 @@ fn test_batch_create_vaults() { asset: asset.clone(), name: String::from_str(&e, "Vault"), symbol: String::from_str(&e, "V"), + share_decimals: 7u32, // SEP-41 convention rwa_name: String::from_str(&e, "RWA"), rwa_symbol: String::from_str(&e, "R"), rwa_document_uri: String::from_str(&e, "uri"), diff --git a/soroban-contracts/contracts/vault_factory/src/tests.rs b/soroban-contracts/contracts/vault_factory/src/tests.rs index 7c78c7e..c0efb82 100644 --- a/soroban-contracts/contracts/vault_factory/src/tests.rs +++ b/soroban-contracts/contracts/vault_factory/src/tests.rs @@ -436,6 +436,7 @@ fn test_batch_create_vaults_exceeds_limit() { asset: asset.clone(), name: String::from_str(&e, "V"), symbol: String::from_str(&e, "V"), + share_decimals: 7u32, // SEP-41 convention rwa_name: String::from_str(&e, "RWA"), rwa_symbol: String::from_str(&e, "R"), rwa_document_uri: String::from_str(&e, "https://example.com"), @@ -470,6 +471,7 @@ fn test_batch_create_vaults_at_limit_ok() { asset: asset.clone(), name: String::from_str(&e, "V"), symbol: String::from_str(&e, "V"), + share_decimals: 7u32, // SEP-41 convention rwa_name: String::from_str(&e, "RWA"), rwa_symbol: String::from_str(&e, "R"), rwa_document_uri: String::from_str(&e, "https://example.com"), diff --git a/soroban-contracts/contracts/vault_factory/src/types.rs b/soroban-contracts/contracts/vault_factory/src/types.rs index ebaa152..4459d43 100644 --- a/soroban-contracts/contracts/vault_factory/src/types.rs +++ b/soroban-contracts/contracts/vault_factory/src/types.rs @@ -51,12 +51,26 @@ pub struct SingleRwaVaultInitParams { } /// Parameters for batch vault creation (mirrors BatchVaultParams in Solidity). +/// +/// # Share Decimals +/// `share_decimals` determines the decimal precision of the vault's share token. +/// This is independent of the underlying asset's decimals. Common conventions: +/// - 6: USDC convention (matching USDC/USDT) +/// - 7: SEP-41 convention (Stellar ecosystem standard for asset-anchored tokens) +/// - 8: BTC-pegged tokens convention +/// - 18: ETH/ERC-20 convention (for compatibility with ETH-based systems) +/// +/// The share/asset ratio is calculated based on these decimals, so using the +/// wrong value will result in incorrect share pricing. #[contracttype] #[derive(Clone, Debug)] pub struct BatchVaultParams { pub asset: Address, pub name: String, pub symbol: String, + /// Number of decimal places for the vault's share token. + /// Default: 7u32 (SEP-41 convention). Set to 6u32 for USDC-aligned assets. + pub share_decimals: u32, pub rwa_name: String, pub rwa_symbol: String, pub rwa_document_uri: String,