arbos/programs/
params.rs

1use alloy_primitives::B256;
2use revm::Database;
3
4use arb_storage::Storage;
5
6/// ArbOS version constants for feature gating.
7pub const ARBOS_VERSION_40: u64 = 40;
8pub const ARBOS_VERSION_50: u64 = 50;
9pub const ARBOS_VERSION_STYLUS_CONTRACT_LIMIT: u64 = 41;
10
11// Initial parameter values.
12const INITIAL_MAX_WASM_SIZE: u32 = 128 * 1024;
13const INITIAL_STACK_DEPTH: u32 = 4 * 65536;
14pub const INITIAL_FREE_PAGES: u16 = 2;
15pub const INITIAL_PAGE_GAS: u16 = 1000;
16pub const INITIAL_PAGE_RAMP: u64 = 620674314;
17const INITIAL_PAGE_LIMIT: u16 = 128;
18const INITIAL_INK_PRICE: u32 = 10000;
19const INITIAL_MAX_FRAGMENT_COUNT: u8 = 2;
20const INITIAL_MIN_INIT_GAS: u8 = 72;
21const INITIAL_MIN_CACHED_GAS: u8 = 11;
22const INITIAL_INIT_COST_SCALAR: u8 = 50;
23const INITIAL_CACHED_COST_SCALAR: u8 = 50;
24const INITIAL_EXPIRY_DAYS: u16 = 365;
25const INITIAL_KEEPALIVE_DAYS: u16 = 31;
26const INITIAL_RECENT_CACHE_SIZE: u16 = 32;
27
28const V2_MIN_INIT_GAS: u8 = 69;
29
30pub const MIN_CACHED_GAS_UNITS: u64 = 32;
31pub const MIN_INIT_GAS_UNITS: u64 = 128;
32pub const COST_SCALAR_PERCENT: u64 = 2;
33
34const ARBOS_50_MAX_WASM_SIZE: u32 = 22000;
35
36/// Stylus configuration parameters packed into storage words.
37#[derive(Debug, Clone)]
38pub struct StylusParams {
39    pub arbos_version: u64,
40    pub version: u16,
41    pub ink_price: u32, // uint24 in Go, stored as u32
42    pub max_stack_depth: u32,
43    pub free_pages: u16,
44    pub page_gas: u16,
45    pub page_ramp: u64,
46    pub page_limit: u16,
47    pub min_init_gas: u8,
48    pub min_cached_init_gas: u8,
49    pub init_cost_scalar: u8,
50    pub cached_cost_scalar: u8,
51    pub expiry_days: u16,
52    pub keepalive_days: u16,
53    pub block_cache_size: u16,
54    pub max_wasm_size: u32,
55    pub max_fragment_count: u8,
56}
57
58impl StylusParams {
59    /// Deserialize params from a storage substorage.
60    pub fn load<D: Database>(arbos_version: u64, sto: &Storage<D>) -> Result<Self, ()> {
61        let mut reader = PackedReader::new(sto);
62
63        let mut params = StylusParams {
64            arbos_version,
65            version: reader.take_u16()?,
66            ink_price: reader.take_u24()?,
67            max_stack_depth: reader.take_u32()?,
68            free_pages: reader.take_u16()?,
69            page_gas: reader.take_u16()?,
70            page_ramp: INITIAL_PAGE_RAMP,
71            page_limit: reader.take_u16()?,
72            min_init_gas: reader.take_u8()?,
73            min_cached_init_gas: reader.take_u8()?,
74            init_cost_scalar: reader.take_u8()?,
75            cached_cost_scalar: reader.take_u8()?,
76            expiry_days: reader.take_u16()?,
77            keepalive_days: reader.take_u16()?,
78            block_cache_size: reader.take_u16()?,
79            max_wasm_size: 0,
80            max_fragment_count: 0,
81        };
82
83        if arbos_version >= ARBOS_VERSION_40 {
84            params.max_wasm_size = reader.take_u32()?;
85        } else {
86            params.max_wasm_size = INITIAL_MAX_WASM_SIZE;
87        }
88        if arbos_version >= ARBOS_VERSION_STYLUS_CONTRACT_LIMIT {
89            params.max_fragment_count = reader.take_u8()?;
90        }
91
92        Ok(params)
93    }
94
95    /// Serialize and persist params to storage.
96    pub fn save<D: Database>(&self, sto: &Storage<D>) -> Result<(), ()> {
97        let mut data = Vec::with_capacity(32);
98
99        data.extend_from_slice(&self.version.to_be_bytes());
100        // uint24: 3 bytes big-endian
101        data.push((self.ink_price >> 16) as u8);
102        data.push((self.ink_price >> 8) as u8);
103        data.push(self.ink_price as u8);
104        data.extend_from_slice(&self.max_stack_depth.to_be_bytes());
105        data.extend_from_slice(&self.free_pages.to_be_bytes());
106        data.extend_from_slice(&self.page_gas.to_be_bytes());
107        data.extend_from_slice(&self.page_limit.to_be_bytes());
108        data.push(self.min_init_gas);
109        data.push(self.min_cached_init_gas);
110        data.push(self.init_cost_scalar);
111        data.push(self.cached_cost_scalar);
112        data.extend_from_slice(&self.expiry_days.to_be_bytes());
113        data.extend_from_slice(&self.keepalive_days.to_be_bytes());
114        data.extend_from_slice(&self.block_cache_size.to_be_bytes());
115
116        if self.arbos_version >= ARBOS_VERSION_40 {
117            data.extend_from_slice(&self.max_wasm_size.to_be_bytes());
118        }
119        if self.arbos_version >= ARBOS_VERSION_STYLUS_CONTRACT_LIMIT {
120            data.push(self.max_fragment_count);
121        }
122
123        let mut slot = 0u64;
124        let mut offset = 0;
125        while offset < data.len() {
126            let end = (offset + 32).min(data.len());
127            let chunk = &data[offset..end];
128
129            let mut word = [0u8; 32];
130            word[..chunk.len()].copy_from_slice(chunk);
131            sto.set_by_uint64(slot, B256::from(word))?;
132
133            slot += 1;
134            offset += 32;
135        }
136        Ok(())
137    }
138
139    /// Upgrade the params version (e.g. 1 -> 2).
140    pub fn upgrade_to_version(&mut self, version: u16) -> Result<(), &'static str> {
141        match version {
142            2 => {
143                if self.version != 1 {
144                    return Err("unexpected version for upgrade to 2");
145                }
146                self.version = 2;
147                self.min_init_gas = V2_MIN_INIT_GAS;
148                Ok(())
149            }
150            _ => Err("unsupported version upgrade"),
151        }
152    }
153
154    /// Handle ArbOS version upgrades that affect stylus params.
155    pub fn upgrade_to_arbos_version(&mut self, new_arbos_version: u64) -> Result<(), &'static str> {
156        if self.arbos_version >= new_arbos_version {
157            return Err("unexpected arbos version downgrade");
158        }
159
160        match new_arbos_version {
161            ARBOS_VERSION_50 => {
162                if self.max_stack_depth > ARBOS_50_MAX_WASM_SIZE {
163                    self.max_stack_depth = ARBOS_50_MAX_WASM_SIZE;
164                }
165            }
166            ARBOS_VERSION_40 => {
167                if self.version != 2 {
168                    return Err("unexpected stylus version for arbos 40 upgrade");
169                }
170                self.max_wasm_size = INITIAL_MAX_WASM_SIZE;
171            }
172            ARBOS_VERSION_STYLUS_CONTRACT_LIMIT => {
173                self.max_fragment_count = INITIAL_MAX_FRAGMENT_COUNT;
174            }
175            _ => {}
176        }
177
178        self.arbos_version = new_arbos_version;
179        Ok(())
180    }
181}
182
183/// Initialize default stylus params and persist to storage.
184pub fn init_stylus_params<D: Database>(arbos_version: u64, sto: &Storage<D>) {
185    let mut params = StylusParams {
186        arbos_version,
187        version: 1,
188        ink_price: INITIAL_INK_PRICE,
189        max_stack_depth: INITIAL_STACK_DEPTH,
190        free_pages: INITIAL_FREE_PAGES,
191        page_gas: INITIAL_PAGE_GAS,
192        page_ramp: INITIAL_PAGE_RAMP,
193        page_limit: INITIAL_PAGE_LIMIT,
194        min_init_gas: INITIAL_MIN_INIT_GAS,
195        min_cached_init_gas: INITIAL_MIN_CACHED_GAS,
196        init_cost_scalar: INITIAL_INIT_COST_SCALAR,
197        cached_cost_scalar: INITIAL_CACHED_COST_SCALAR,
198        expiry_days: INITIAL_EXPIRY_DAYS,
199        keepalive_days: INITIAL_KEEPALIVE_DAYS,
200        block_cache_size: INITIAL_RECENT_CACHE_SIZE,
201        max_wasm_size: 0,
202        max_fragment_count: 0,
203    };
204    if arbos_version >= ARBOS_VERSION_40 {
205        params.max_wasm_size = INITIAL_MAX_WASM_SIZE;
206    }
207    if arbos_version >= ARBOS_VERSION_STYLUS_CONTRACT_LIMIT {
208        params.max_fragment_count = INITIAL_MAX_FRAGMENT_COUNT;
209    }
210    let _ = params.save(sto);
211}
212
213/// Helper to read packed fields from sequential storage words.
214struct PackedReader<'a, D> {
215    sto: &'a Storage<D>,
216    slot: u64,
217    buf: [u8; 32],
218    pos: usize,
219}
220
221impl<'a, D: Database> PackedReader<'a, D> {
222    fn new(sto: &'a Storage<D>) -> Self {
223        Self {
224            sto,
225            slot: 0,
226            buf: [0u8; 32],
227            pos: 32, // force first read
228        }
229    }
230
231    fn ensure(&mut self, count: usize) -> Result<(), ()> {
232        if self.pos + count > 32 {
233            let word = self.sto.get_by_uint64(self.slot)?;
234            self.buf = word.0;
235            self.slot += 1;
236            self.pos = 0;
237        }
238        Ok(())
239    }
240
241    fn take_bytes(&mut self, count: usize) -> Result<&[u8], ()> {
242        self.ensure(count)?;
243        let start = self.pos;
244        self.pos += count;
245        Ok(&self.buf[start..self.pos])
246    }
247
248    fn take_u8(&mut self) -> Result<u8, ()> {
249        let bytes = self.take_bytes(1)?;
250        Ok(bytes[0])
251    }
252
253    fn take_u16(&mut self) -> Result<u16, ()> {
254        let bytes = self.take_bytes(2)?;
255        Ok(u16::from_be_bytes([bytes[0], bytes[1]]))
256    }
257
258    fn take_u24(&mut self) -> Result<u32, ()> {
259        let bytes = self.take_bytes(3)?;
260        Ok((bytes[0] as u32) << 16 | (bytes[1] as u32) << 8 | bytes[2] as u32)
261    }
262
263    fn take_u32(&mut self) -> Result<u32, ()> {
264        let bytes = self.take_bytes(4)?;
265        Ok(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
266    }
267}