1use arb_storage::{Storage, StorageBackend, SystemStateBackend};
2
3use super::ProgramsError;
4
5pub const ARBOS_VERSION_40: u64 = 40;
7pub const ARBOS_VERSION_50: u64 = 50;
8pub const ARBOS_VERSION_STYLUS_CONTRACT_LIMIT: u64 = 60;
9
10const INITIAL_MAX_WASM_SIZE: u32 = 128 * 1024;
12const INITIAL_STACK_DEPTH: u32 = 4 * 65536;
13pub const INITIAL_FREE_PAGES: u16 = 2;
14pub const INITIAL_PAGE_GAS: u16 = 1000;
15pub const INITIAL_PAGE_RAMP: u64 = 620674314;
16const INITIAL_PAGE_LIMIT: u16 = 128;
17const INITIAL_INK_PRICE: u32 = 10000;
18pub const INITIAL_MAX_FRAGMENT_COUNT: u8 = 4;
19const INITIAL_MIN_INIT_GAS: u8 = 72;
20const INITIAL_MIN_CACHED_GAS: u8 = 11;
21const INITIAL_INIT_COST_SCALAR: u8 = 50;
22const INITIAL_CACHED_COST_SCALAR: u8 = 50;
23const INITIAL_EXPIRY_DAYS: u16 = 365;
24const INITIAL_KEEPALIVE_DAYS: u16 = 31;
25const INITIAL_RECENT_CACHE_SIZE: u16 = 32;
26
27const V2_MIN_INIT_GAS: u8 = 69;
28
29pub const MIN_CACHED_GAS_UNITS: u64 = 32;
30pub const MIN_INIT_GAS_UNITS: u64 = 128;
31pub const COST_SCALAR_PERCENT: u64 = 2;
32
33const ARBOS_50_MAX_STACK_DEPTH: u32 = 22000;
34const ARBOS_60_MAX_WASM_SIZE: u32 = 256 * 1024;
35
36#[derive(Debug, Clone)]
38pub struct StylusParams {
39 pub arbos_version: u64,
40 pub version: u16,
41 pub ink_price: u32, 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 pub fn load<D, B: SystemStateBackend>(
61 arbos_version: u64,
62 sto: &Storage<'_, D>,
63 backend: &mut B,
64 ) -> Result<Self, ProgramsError> {
65 Self::load_from_reader(arbos_version, PackedReader::new(sto, backend))
66 }
67
68 fn load_from_reader<R: PackedRead>(
69 arbos_version: u64,
70 mut reader: R,
71 ) -> Result<Self, ProgramsError> {
72 let mut params = StylusParams {
73 arbos_version,
74 version: reader.take_u16()?,
75 ink_price: reader.take_u24()?,
76 max_stack_depth: reader.take_u32()?,
77 free_pages: reader.take_u16()?,
78 page_gas: reader.take_u16()?,
79 page_ramp: INITIAL_PAGE_RAMP,
80 page_limit: reader.take_u16()?,
81 min_init_gas: reader.take_u8()?,
82 min_cached_init_gas: reader.take_u8()?,
83 init_cost_scalar: reader.take_u8()?,
84 cached_cost_scalar: reader.take_u8()?,
85 expiry_days: reader.take_u16()?,
86 keepalive_days: reader.take_u16()?,
87 block_cache_size: reader.take_u16()?,
88 max_wasm_size: 0,
89 max_fragment_count: 0,
90 };
91
92 if arbos_version >= ARBOS_VERSION_40 {
93 params.max_wasm_size = reader.take_u32()?;
94 } else {
95 params.max_wasm_size = INITIAL_MAX_WASM_SIZE;
96 }
97 if arbos_version >= ARBOS_VERSION_STYLUS_CONTRACT_LIMIT {
98 params.max_fragment_count = reader.take_u8()?;
99 }
100
101 Ok(params)
102 }
103
104 pub fn save<D, B: StorageBackend>(
106 &self,
107 sto: &Storage<'_, D>,
108 backend: &mut B,
109 ) -> Result<(), ProgramsError> {
110 use alloy_primitives::U256;
111 let data = self.encode();
112 let mut slot = 0u64;
113 let mut offset = 0;
114 while offset < data.len() {
115 let end = (offset + 32).min(data.len());
116 let chunk = &data[offset..end];
117
118 let mut word = [0u8; 32];
119 word[..chunk.len()].copy_from_slice(chunk);
120 let storage_slot = sto.new_slot(slot);
121 backend
122 .sstore(sto.account(), storage_slot, U256::from_be_bytes(word))
123 .map_err(Into::into)?;
124
125 slot += 1;
126 offset += 32;
127 }
128 Ok(())
129 }
130
131 fn encode(&self) -> Vec<u8> {
132 let mut data = Vec::with_capacity(32);
133
134 data.extend_from_slice(&self.version.to_be_bytes());
135 data.push((self.ink_price >> 16) as u8);
137 data.push((self.ink_price >> 8) as u8);
138 data.push(self.ink_price as u8);
139 data.extend_from_slice(&self.max_stack_depth.to_be_bytes());
140 data.extend_from_slice(&self.free_pages.to_be_bytes());
141 data.extend_from_slice(&self.page_gas.to_be_bytes());
142 data.extend_from_slice(&self.page_limit.to_be_bytes());
143 data.push(self.min_init_gas);
144 data.push(self.min_cached_init_gas);
145 data.push(self.init_cost_scalar);
146 data.push(self.cached_cost_scalar);
147 data.extend_from_slice(&self.expiry_days.to_be_bytes());
148 data.extend_from_slice(&self.keepalive_days.to_be_bytes());
149 data.extend_from_slice(&self.block_cache_size.to_be_bytes());
150
151 if self.arbos_version >= ARBOS_VERSION_40 {
152 data.extend_from_slice(&self.max_wasm_size.to_be_bytes());
153 }
154 if self.arbos_version >= ARBOS_VERSION_STYLUS_CONTRACT_LIMIT {
155 data.push(self.max_fragment_count);
156 }
157 data
158 }
159
160 pub fn upgrade_to_version(&mut self, version: u16) -> Result<(), ProgramsError> {
162 match version {
163 2 => {
164 if self.version != 1 {
165 return Err(ProgramsError::InvalidParamsUpgrade(
166 "unexpected version for upgrade to 2",
167 ));
168 }
169 self.version = 2;
170 self.min_init_gas = V2_MIN_INIT_GAS;
171 Ok(())
172 }
173 3 => {
174 if self.version != 2 {
175 return Err(ProgramsError::InvalidParamsUpgrade(
176 "unexpected version for upgrade to 3",
177 ));
178 }
179 self.version = 3;
180 Ok(())
181 }
182 _ => Err(ProgramsError::InvalidParamsUpgrade(
183 "unsupported version upgrade",
184 )),
185 }
186 }
187
188 pub fn upgrade_to_arbos_version(
190 &mut self,
191 new_arbos_version: u64,
192 ) -> Result<(), ProgramsError> {
193 if self.arbos_version >= new_arbos_version {
194 return Err(ProgramsError::InvalidParamsUpgrade(
195 "unexpected arbos version downgrade",
196 ));
197 }
198
199 match new_arbos_version {
200 ARBOS_VERSION_50 => {
201 if self.max_stack_depth > ARBOS_50_MAX_STACK_DEPTH {
202 self.max_stack_depth = ARBOS_50_MAX_STACK_DEPTH;
203 }
204 }
205 ARBOS_VERSION_40 => {
206 if self.version != 2 {
207 return Err(ProgramsError::InvalidParamsUpgrade(
208 "unexpected stylus version for arbos 40 upgrade",
209 ));
210 }
211 self.max_wasm_size = INITIAL_MAX_WASM_SIZE;
212 }
213 ARBOS_VERSION_STYLUS_CONTRACT_LIMIT => {
214 self.max_wasm_size = ARBOS_60_MAX_WASM_SIZE;
215 self.max_fragment_count = INITIAL_MAX_FRAGMENT_COUNT;
216 }
217 _ => {}
218 }
219
220 self.arbos_version = new_arbos_version;
221 Ok(())
222 }
223}
224
225pub fn init_stylus_params<D, B: StorageBackend>(
227 arbos_version: u64,
228 sto: &Storage<'_, D>,
229 backend: &mut B,
230) -> Result<(), ProgramsError> {
231 let mut params = StylusParams {
232 arbos_version,
233 version: 1,
234 ink_price: INITIAL_INK_PRICE,
235 max_stack_depth: INITIAL_STACK_DEPTH,
236 free_pages: INITIAL_FREE_PAGES,
237 page_gas: INITIAL_PAGE_GAS,
238 page_ramp: INITIAL_PAGE_RAMP,
239 page_limit: INITIAL_PAGE_LIMIT,
240 min_init_gas: INITIAL_MIN_INIT_GAS,
241 min_cached_init_gas: INITIAL_MIN_CACHED_GAS,
242 init_cost_scalar: INITIAL_INIT_COST_SCALAR,
243 cached_cost_scalar: INITIAL_CACHED_COST_SCALAR,
244 expiry_days: INITIAL_EXPIRY_DAYS,
245 keepalive_days: INITIAL_KEEPALIVE_DAYS,
246 block_cache_size: INITIAL_RECENT_CACHE_SIZE,
247 max_wasm_size: 0,
248 max_fragment_count: 0,
249 };
250 if arbos_version >= ARBOS_VERSION_STYLUS_CONTRACT_LIMIT {
251 params.max_wasm_size = ARBOS_60_MAX_WASM_SIZE;
252 } else if arbos_version >= ARBOS_VERSION_40 {
253 params.max_wasm_size = INITIAL_MAX_WASM_SIZE;
254 }
255 if arbos_version >= ARBOS_VERSION_STYLUS_CONTRACT_LIMIT {
256 params.max_fragment_count = INITIAL_MAX_FRAGMENT_COUNT;
257 }
258 params.save(sto, backend)
259}
260
261trait PackedRead {
262 fn take_u8(&mut self) -> Result<u8, ProgramsError>;
263 fn take_u16(&mut self) -> Result<u16, ProgramsError>;
264 fn take_u24(&mut self) -> Result<u32, ProgramsError>;
265 fn take_u32(&mut self) -> Result<u32, ProgramsError>;
266}
267
268struct PackedReader<'a, 'b, 'c, D, B: SystemStateBackend> {
269 sto: &'a Storage<'c, D>,
270 backend: &'b mut B,
271 slot: u64,
272 buf: [u8; 32],
273 pos: usize,
274}
275
276impl<'a, 'b, 'c, D, B: SystemStateBackend> PackedReader<'a, 'b, 'c, D, B> {
277 fn new(sto: &'a Storage<'c, D>, backend: &'b mut B) -> Self {
278 Self {
279 sto,
280 backend,
281 slot: 0,
282 buf: [0u8; 32],
283 pos: 32,
284 }
285 }
286
287 fn ensure(&mut self, count: usize) -> Result<(), ProgramsError> {
288 if self.pos + count > 32 {
289 let slot = self.sto.new_slot(self.slot);
290 let value = self
291 .backend
292 .sload_system(self.sto.account(), slot)
293 .map_err(Into::into)?;
294 self.buf = value.to_be_bytes::<32>();
295 self.slot += 1;
296 self.pos = 0;
297 }
298 Ok(())
299 }
300
301 fn take_bytes(&mut self, count: usize) -> Result<&[u8], ProgramsError> {
302 self.ensure(count)?;
303 let start = self.pos;
304 self.pos += count;
305 Ok(&self.buf[start..self.pos])
306 }
307}
308
309impl<D, B: SystemStateBackend> PackedRead for PackedReader<'_, '_, '_, D, B> {
310 fn take_u8(&mut self) -> Result<u8, ProgramsError> {
311 let bytes = self.take_bytes(1)?;
312 Ok(bytes[0])
313 }
314
315 fn take_u16(&mut self) -> Result<u16, ProgramsError> {
316 let bytes = self.take_bytes(2)?;
317 Ok(u16::from_be_bytes([bytes[0], bytes[1]]))
318 }
319
320 fn take_u24(&mut self) -> Result<u32, ProgramsError> {
321 let bytes = self.take_bytes(3)?;
322 Ok((bytes[0] as u32) << 16 | (bytes[1] as u32) << 8 | bytes[2] as u32)
323 }
324
325 fn take_u32(&mut self) -> Result<u32, ProgramsError> {
326 let bytes = self.take_bytes(4)?;
327 Ok(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
328 }
329}