arb_stylus/
config.rs

1use super::ink::Gas;
2use crate::{error::StylusError, ink::Ink};
3
4/// Runtime configuration for a Stylus program execution.
5#[derive(Clone, Copy, Debug)]
6pub struct StylusConfig {
7    /// Version the program was compiled against.
8    pub version: u16,
9    /// Maximum stack depth in words.
10    pub max_depth: u32,
11    /// Pricing parameters for ink/gas conversion.
12    pub pricing: PricingParams,
13}
14
15impl Default for StylusConfig {
16    fn default() -> Self {
17        Self {
18            version: 0,
19            max_depth: u32::MAX,
20            pricing: PricingParams::default(),
21        }
22    }
23}
24
25impl StylusConfig {
26    pub const fn new(version: u16, max_depth: u32, ink_price: u32) -> Self {
27        Self {
28            version,
29            max_depth,
30            pricing: PricingParams::new(ink_price),
31        }
32    }
33}
34
35/// Pricing parameters for ink/gas conversion.
36#[derive(Clone, Copy, Debug)]
37pub struct PricingParams {
38    /// The price of ink, measured in bips of an EVM gas.
39    pub ink_price: u32,
40}
41
42impl Default for PricingParams {
43    fn default() -> Self {
44        Self { ink_price: 1 }
45    }
46}
47
48impl PricingParams {
49    pub const fn new(ink_price: u32) -> Self {
50        Self { ink_price }
51    }
52
53    /// Convert EVM gas to ink.
54    pub fn gas_to_ink(&self, gas: Gas) -> Ink {
55        Ink(gas.0.saturating_mul(self.ink_price as u64))
56    }
57
58    /// Convert ink to EVM gas.
59    pub fn ink_to_gas(&self, ink: Ink) -> Gas {
60        Gas(ink.0 / self.ink_price as u64)
61    }
62}
63
64/// Compile-time configuration for WASM module compilation.
65#[derive(Clone, Debug, Default)]
66pub struct CompileConfig {
67    /// Version of the compiler to use.
68    pub version: u16,
69    /// Pricing parameters for metering.
70    pub pricing: CompilePricingParams,
71    /// Memory bounds.
72    pub bounds: CompileMemoryParams,
73    /// Debug parameters.
74    pub debug: CompileDebugParams,
75}
76
77/// Memory bounds for WASM compilation.
78#[derive(Clone, Copy, Debug)]
79pub struct CompileMemoryParams {
80    /// Maximum number of WASM pages a program may start with.
81    pub heap_bound: u32,
82    /// Maximum size of a stack frame in words.
83    pub max_frame_size: u32,
84    /// Maximum overlapping value lifetimes in a frame.
85    pub max_frame_contention: u16,
86}
87
88impl Default for CompileMemoryParams {
89    fn default() -> Self {
90        Self {
91            heap_bound: u32::MAX / 65536, // Pages(u32::MAX / WASM_PAGE_SIZE)
92            max_frame_size: u32::MAX,
93            max_frame_contention: u16::MAX,
94        }
95    }
96}
97
98/// Pricing parameters for WASM compilation.
99#[derive(Clone, Debug, Default)]
100pub struct CompilePricingParams {
101    /// Cost of checking the amount of ink left.
102    pub ink_header_cost: u64,
103    /// Per-byte MemoryFill cost.
104    pub memory_fill_ink: u64,
105    /// Per-byte MemoryCopy cost.
106    pub memory_copy_ink: u64,
107}
108
109/// Debug parameters for WASM compilation.
110#[derive(Clone, Debug, Default)]
111pub struct CompileDebugParams {
112    /// Allow debug functions (console.log, etc.).
113    pub debug_funcs: bool,
114    /// Retain debug info in compiled modules.
115    pub debug_info: bool,
116    /// Add instrumentation to count opcode executions.
117    pub count_ops: bool,
118}
119
120impl CompileConfig {
121    /// Create a versioned compile config.
122    ///
123    /// Returns [`StylusError::UnsupportedDictionaryVersion`] when `version`
124    /// falls outside the range this build understands. The version is read
125    /// from contract storage and therefore must not panic the executor.
126    pub fn version(version: u16, debug_chain: bool) -> Result<Self, StylusError> {
127        let mut config = Self {
128            version,
129            debug: CompileDebugParams {
130                debug_funcs: debug_chain,
131                debug_info: debug_chain,
132                ..Default::default()
133            },
134            ..Default::default()
135        };
136
137        match version {
138            0 => {}
139            1..=3 => {
140                config.bounds.heap_bound = 128; // 128 pages = 8 MB
141                config.bounds.max_frame_size = 10 * 1024;
142                config.bounds.max_frame_contention = 4096;
143                config.pricing = CompilePricingParams {
144                    ink_header_cost: 2450,
145                    memory_fill_ink: 800 / 8,
146                    memory_copy_ink: 800 / 8,
147                };
148            }
149            _ => return Err(StylusError::UnsupportedDictionaryVersion(version)),
150        }
151
152        Ok(config)
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn unsupported_version_returns_typed_error() {
162        let err = CompileConfig::version(99, false).expect_err("v99 must be unsupported");
163        assert!(
164            matches!(err, StylusError::UnsupportedDictionaryVersion(99)),
165            "expected UnsupportedDictionaryVersion(99), got {err:?}",
166        );
167    }
168
169    #[test]
170    fn supported_versions_succeed() {
171        for v in 0u16..=3 {
172            let cfg = CompileConfig::version(v, false)
173                .unwrap_or_else(|e| panic!("version {v} must be supported, got {e:?}"));
174            assert_eq!(cfg.version, v);
175        }
176    }
177
178    #[test]
179    fn version_boundary_above_supported_is_rejected() {
180        let err = CompileConfig::version(4, false).expect_err("v4 must be unsupported");
181        assert!(matches!(err, StylusError::UnsupportedDictionaryVersion(4)));
182    }
183}