Skip to main content

algo_sdk/
config.rs

1//! Runtime parameter configuration — live-tunable strategy parameters.
2//!
3//! Strategies declare typed parameters that can be adjusted at runtime
4//! without redeploying WASM. The host writes parameters to a fixed WASM
5//! memory region; the strategy reads them on each tick.
6//!
7//! # Example
8//! ```rust,ignore
9//! use algo_sdk::config::ConfigRegion;
10//!
11//! // In your strategy's on_tick:
12//! let config = unsafe { ConfigRegion::read() };
13//! let spread_target_bps = config.get_i32(0);  // param slot 0
14//! let max_position = config.get_i64(1);        // param slot 1
15//! ```
16
17/// WASM memory offset for the config region.
18/// Located after PoolStateTable (0x23000 + 3088 = ~0x23C10). Safe gap to 0x24000.
19pub const CONFIG_REGION_WASM_OFFSET: u32 = 0x24000;
20
21/// Maximum number of parameter slots.
22pub const MAX_CONFIG_PARAMS: usize = 32;
23
24/// Runtime configuration region — read by strategy, written by host.
25///
26/// Fixed-size (528 bytes). Each slot is 8 bytes (i64) with a type tag.
27/// Slot 0..MAX_CONFIG_PARAMS-1 are available for user-defined parameters.
28#[derive(Clone, Copy)]
29#[repr(C)]
30pub struct ConfigRegion {
31    /// ABI version (currently 1).
32    pub version: u8,
33    /// Number of populated parameter slots.
34    pub param_count: u8,
35    /// Incremented on every config update (strategy can detect changes).
36    pub update_seq: u16,
37    pub _pad: [u8; 4],
38    /// Timestamp of last config update (nanoseconds since epoch).
39    pub update_ns: u64,
40    /// Parameter values (i64, interpret per your schema).
41    pub params: [i64; MAX_CONFIG_PARAMS],
42    /// Parameter type tags: 0=i64, 1=f64_bits, 2=bool (0/1), 3=bps (i32 in lower 32 bits).
43    pub types: [u8; MAX_CONFIG_PARAMS],
44}
45
46impl Default for ConfigRegion {
47    fn default() -> Self {
48        Self {
49            version: 1,
50            param_count: 0,
51            update_seq: 0,
52            _pad: [0; 4],
53            update_ns: 0,
54            params: [0; MAX_CONFIG_PARAMS],
55            types: [0; MAX_CONFIG_PARAMS],
56        }
57    }
58}
59
60/// Parameter type tag.
61pub mod ParamType {
62    /// Raw i64.
63    pub const INT: u8 = 0;
64    /// f64 stored as bit pattern (use f64::from_bits).
65    pub const FLOAT: u8 = 1;
66    /// Boolean (0 or 1).
67    pub const BOOL: u8 = 2;
68    /// Basis points (i32 in lower 32 bits).
69    pub const BPS: u8 = 3;
70}
71
72impl ConfigRegion {
73    /// Read the config region from WASM memory.
74    ///
75    /// # Safety
76    /// Only valid inside a WASM callback when the host has written config data.
77    #[cfg(target_arch = "wasm32")]
78    #[inline(always)]
79    pub unsafe fn read() -> &'static Self {
80        &*(CONFIG_REGION_WASM_OFFSET as *const Self)
81    }
82
83    /// Get raw i64 value at slot index.
84    #[inline(always)]
85    pub fn get_i64(&self, slot: usize) -> i64 {
86        if slot < MAX_CONFIG_PARAMS { self.params[slot] } else { 0 }
87    }
88
89    /// Get i32 value at slot index (lower 32 bits).
90    #[inline(always)]
91    pub fn get_i32(&self, slot: usize) -> i32 {
92        self.get_i64(slot) as i32
93    }
94
95    /// Get f64 value at slot index (from bit pattern).
96    #[inline(always)]
97    pub fn get_f64(&self, slot: usize) -> f64 {
98        f64::from_bits(self.get_i64(slot) as u64)
99    }
100
101    /// Get bool value at slot index.
102    #[inline(always)]
103    pub fn get_bool(&self, slot: usize) -> bool {
104        self.get_i64(slot) != 0
105    }
106
107    /// Get basis points value at slot index.
108    #[inline(always)]
109    pub fn get_bps(&self, slot: usize) -> i32 {
110        self.get_i32(slot)
111    }
112
113    /// Whether config has been updated (compare against last seen seq).
114    #[inline(always)]
115    pub fn is_updated(&self, last_seq: u16) -> bool {
116        self.update_seq != last_seq
117    }
118
119    /// Number of populated parameters.
120    #[inline(always)]
121    pub fn count(&self) -> usize {
122        self.param_count as usize
123    }
124}
125
126// Compile-time ABI check
127const _: () = assert!(
128    core::mem::size_of::<ConfigRegion>() == 304,
129    "ConfigRegion must be exactly 304 bytes"
130);
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn config_region_size() {
138        // 1 + 1 + 2 + 4 + 8 + 32*8 + 32 = 304
139        assert_eq!(core::mem::size_of::<ConfigRegion>(), 304);
140    }
141
142    #[test]
143    fn config_region_get_params() {
144        let mut config = ConfigRegion::default();
145        config.param_count = 3;
146        config.params[0] = 50; // spread target bps
147        config.params[1] = 1_000_000_000; // max position 1e8
148        config.params[2] = f64::to_bits(0.5) as i64; // some float param
149        config.types[0] = ParamType::BPS;
150        config.types[1] = ParamType::INT;
151        config.types[2] = ParamType::FLOAT;
152
153        assert_eq!(config.get_bps(0), 50);
154        assert_eq!(config.get_i64(1), 1_000_000_000);
155        assert!((config.get_f64(2) - 0.5).abs() < 0.001);
156    }
157
158    #[test]
159    fn config_region_update_detection() {
160        let mut config = ConfigRegion::default();
161        assert!(!config.is_updated(0));
162        config.update_seq = 1;
163        assert!(config.is_updated(0));
164        assert!(!config.is_updated(1));
165    }
166
167    #[test]
168    fn config_region_out_of_bounds() {
169        let config = ConfigRegion::default();
170        assert_eq!(config.get_i64(99), 0);
171        assert_eq!(config.get_i32(99), 0);
172    }
173}