1use alloy_primitives::B256;
2use revm::Database;
3
4use arb_storage::Storage;
5
6pub const ARBOS_VERSION_40: u64 = 40;
8pub const ARBOS_VERSION_50: u64 = 50;
9pub const ARBOS_VERSION_STYLUS_CONTRACT_LIMIT: u64 = 41;
10
11const 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#[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: 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 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 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 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 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
183pub 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
213struct 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, }
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}