Skip to main content

avm_rs/opcodes/
stack.rs

1//! Stack manipulation opcodes
2
3use crate::error::{AvmError, AvmResult};
4use crate::types::StackValue;
5use crate::vm::EvalContext;
6
7/// Pop the top stack value
8pub fn op_pop(ctx: &mut EvalContext) -> AvmResult<()> {
9    ctx.pop()?;
10    ctx.advance_pc(1)?;
11    Ok(())
12}
13
14/// Duplicate the top stack value
15pub fn op_dup(ctx: &mut EvalContext) -> AvmResult<()> {
16    let val = ctx.peek()?.clone();
17    ctx.push(val)?;
18    ctx.advance_pc(1)?;
19    Ok(())
20}
21
22/// Duplicate the top two stack values according to TEAL spec: [A, B] -> [A, B, A, B]
23pub fn op_dup2(ctx: &mut EvalContext) -> AvmResult<()> {
24    if ctx.stack_size() < 2 {
25        return Err(AvmError::StackUnderflow);
26    }
27
28    // Duplicate the top two values according to TEAL spec: [A, B] -> [A, B, A, B]
29    // This matches go-algorand implementation: cx.Stack = append(cx.Stack, cx.Stack[prev:]...)
30    let b = ctx.pop()?; // Pop top value
31    let a = ctx.pop()?; // Pop second value
32
33    // Push back original values
34    ctx.push(a.clone())?;
35    ctx.push(b.clone())?;
36
37    // Push duplicates
38    ctx.push(a)?;
39    ctx.push(b)?;
40
41    ctx.advance_pc(1)?;
42    Ok(())
43}
44
45/// Swap the top two stack values
46pub fn op_swap(ctx: &mut EvalContext) -> AvmResult<()> {
47    let b = ctx.pop()?;
48    let a = ctx.pop()?;
49
50    ctx.push(b)?;
51    ctx.push(a)?;
52    ctx.advance_pc(1)?;
53    Ok(())
54}
55
56/// Select between two values based on condition
57pub fn op_select(ctx: &mut EvalContext) -> AvmResult<()> {
58    let c = ctx.pop()?;
59    let b = ctx.pop()?;
60    let a = ctx.pop()?;
61
62    let condition = c.as_bool()?;
63    let result = if condition { a } else { b };
64
65    ctx.push(result)?;
66    ctx.advance_pc(1)?;
67    Ok(())
68}
69
70/// Dig value from stack depth N
71pub fn op_dig(ctx: &mut EvalContext) -> AvmResult<()> {
72    ctx.advance_pc(1)?;
73    // Get the depth from the immediate value
74    let depth = ctx.read_bytes(1)?[0] as usize;
75    ctx.advance_pc(1)?;
76
77    if ctx.stack_size() <= depth {
78        return Err(AvmError::StackUnderflow);
79    }
80
81    // Get the value at depth N from the top and push a copy to the top
82    let value = ctx.peek_at_depth(depth)?.clone();
83    ctx.push(value)?;
84    Ok(())
85}
86
87/// Bury value at stack depth N
88pub fn op_bury(ctx: &mut EvalContext) -> AvmResult<()> {
89    ctx.advance_pc(1)?;
90    // Get the depth from the immediate value
91    let depth = ctx.read_bytes(1)?[0] as usize;
92    ctx.advance_pc(1)?;
93
94    if ctx.stack_size() <= depth {
95        return Err(AvmError::StackUnderflow);
96    }
97
98    // Pop the top value and replace the value at depth N
99    let value = ctx.pop()?;
100    let _ = ctx.remove_at_depth(depth)?; // Remove the old value
101    ctx.insert_at_depth(depth, value)?; // Insert the new value
102    Ok(())
103}
104
105/// Cover N values with top value
106pub fn op_cover(ctx: &mut EvalContext) -> AvmResult<()> {
107    ctx.advance_pc(1)?;
108    let n = ctx.read_bytes(1)?[0] as usize;
109    ctx.advance_pc(1)?;
110
111    if ctx.stack_size() <= n {
112        return Err(AvmError::StackUnderflow);
113    }
114
115    // Pop the top value and insert it at depth N+1
116    let value = ctx.pop()?;
117    ctx.insert_at_depth(n, value)?;
118    Ok(())
119}
120
121/// Uncover N values to top
122pub fn op_uncover(ctx: &mut EvalContext) -> AvmResult<()> {
123    ctx.advance_pc(1)?;
124    let n = ctx.read_bytes(1)?[0] as usize;
125    ctx.advance_pc(1)?;
126
127    if ctx.stack_size() <= n {
128        return Err(AvmError::StackUnderflow);
129    }
130
131    // Remove the value at depth N and push it to the top
132    let value = ctx.remove_at_depth(n)?;
133    ctx.push(value)?;
134    Ok(())
135}
136
137/// Load value from scratch space
138pub fn op_load(ctx: &mut EvalContext) -> AvmResult<()> {
139    ctx.advance_pc(1)?; // advance past opcode
140    let index = ctx.read_bytes(1)?[0];
141    ctx.advance_pc(1)?; // advance past immediate
142
143    let value = ctx.get_scratch(index)?.clone();
144    ctx.push(value)?;
145    Ok(())
146}
147
148/// Store value to scratch space
149pub fn op_store(ctx: &mut EvalContext) -> AvmResult<()> {
150    ctx.advance_pc(1)?; // advance past opcode
151    let index = ctx.read_bytes(1)?[0];
152    ctx.advance_pc(1)?; // advance past immediate
153
154    let value = ctx.pop()?;
155    ctx.set_scratch(index, value)?;
156    Ok(())
157}
158
159/// Get byte array length
160pub fn op_len(ctx: &mut EvalContext) -> AvmResult<()> {
161    let val = ctx.pop()?;
162    let len = match val {
163        StackValue::Bytes(bytes) => bytes.len() as u64,
164        StackValue::Uint(_) => 8, // Uint is 8 bytes
165    };
166
167    ctx.push(StackValue::Uint(len))?;
168    ctx.advance_pc(1)?;
169    Ok(())
170}
171
172/// Convert integer to bytes (big-endian)
173pub fn op_itob(ctx: &mut EvalContext) -> AvmResult<()> {
174    let val = ctx.pop()?;
175    let int_val = val.as_uint()?;
176
177    let bytes = int_val.to_be_bytes().to_vec();
178    ctx.push(StackValue::Bytes(bytes))?;
179    ctx.advance_pc(1)?;
180    Ok(())
181}
182
183/// Convert bytes to integer (big-endian)
184pub fn op_btoi(ctx: &mut EvalContext) -> AvmResult<()> {
185    let val = ctx.pop()?;
186    let bytes = val.as_bytes()?;
187
188    if bytes.is_empty() {
189        ctx.push(StackValue::Uint(0))?;
190        ctx.advance_pc(1)?;
191        return Ok(());
192    }
193
194    if bytes.len() > 8 {
195        return Err(AvmError::InvalidByteArrayLength {
196            expected: 8,
197            actual: bytes.len(),
198        });
199    }
200
201    // Pad with zeros if necessary
202    let mut padded = vec![0u8; 8];
203    let start_idx = 8 - bytes.len();
204    padded[start_idx..].copy_from_slice(bytes);
205
206    let int_val = u64::from_be_bytes(padded.try_into().unwrap());
207    ctx.push(StackValue::Uint(int_val))?;
208    ctx.advance_pc(1)?;
209    Ok(())
210}
211
212/// Concatenate two byte arrays
213pub fn op_concat(ctx: &mut EvalContext) -> AvmResult<()> {
214    let b = ctx.pop()?;
215    let a = ctx.pop()?;
216
217    let a_bytes = a.as_bytes()?;
218    let b_bytes = b.as_bytes()?;
219
220    let mut result = Vec::with_capacity(a_bytes.len() + b_bytes.len());
221    result.extend_from_slice(a_bytes);
222    result.extend_from_slice(b_bytes);
223
224    ctx.push(StackValue::Bytes(result))?;
225    ctx.advance_pc(1)?;
226    Ok(())
227}
228
229/// Extract substring with immediate start and length
230pub fn op_substring(ctx: &mut EvalContext) -> AvmResult<()> {
231    ctx.advance_pc(1)?; // advance past opcode
232    let start = ctx.read_bytes(1)?[0] as usize;
233    ctx.advance_pc(1)?; // advance past start parameter
234    let length = ctx.read_bytes(1)?[0] as usize;
235    ctx.advance_pc(1)?; // advance past length parameter
236
237    let val = ctx.pop()?;
238    let bytes = val.as_bytes()?;
239
240    if start >= bytes.len() || start + length > bytes.len() {
241        return Err(AvmError::InvalidByteArrayLength {
242            expected: start + length,
243            actual: bytes.len(),
244        });
245    }
246
247    let result = bytes[start..start + length].to_vec();
248    ctx.push(StackValue::Bytes(result))?;
249    Ok(())
250}
251
252/// Extract substring with stack arguments
253pub fn op_substring3(ctx: &mut EvalContext) -> AvmResult<()> {
254    let end = ctx.pop()?;
255    let start = ctx.pop()?;
256    let val = ctx.pop()?;
257
258    let start_idx = start.as_uint()? as usize;
259    let end_idx = end.as_uint()? as usize;
260    let bytes = val.as_bytes()?;
261
262    if start_idx >= bytes.len() || end_idx > bytes.len() || start_idx > end_idx {
263        return Err(AvmError::InvalidByteArrayLength {
264            expected: end_idx,
265            actual: bytes.len(),
266        });
267    }
268
269    let result = bytes[start_idx..end_idx].to_vec();
270    ctx.push(StackValue::Bytes(result))?;
271    ctx.advance_pc(1)?;
272    Ok(())
273}
274
275/// Duplicate N values from top of stack
276pub fn op_dupn(ctx: &mut EvalContext) -> AvmResult<()> {
277    ctx.advance_pc(1)?; // advance past opcode
278    let n = ctx.read_bytes(1)?[0] as usize;
279    ctx.advance_pc(1)?; // advance past parameter
280
281    if n == 0 {
282        return Ok(());
283    }
284
285    if ctx.stack_size() < n {
286        return Err(AvmError::StackUnderflow);
287    }
288
289    // This is a simplified implementation - would need direct stack access for efficiency
290    let mut values = Vec::new();
291    for _ in 0..n {
292        values.push(ctx.pop()?);
293    }
294
295    // Restore original values
296    for val in values.iter().rev() {
297        ctx.push(val.clone())?;
298    }
299
300    // Push duplicated values
301    for val in values.iter().rev() {
302        ctx.push(val.clone())?;
303    }
304
305    Ok(())
306}
307
308/// Pop N values from top of stack
309pub fn op_popn(ctx: &mut EvalContext) -> AvmResult<()> {
310    ctx.advance_pc(1)?; // advance past opcode
311    let n = ctx.read_bytes(1)?[0] as usize;
312    ctx.advance_pc(1)?; // advance past parameter
313
314    if ctx.stack_size() < n {
315        return Err(AvmError::StackUnderflow);
316    }
317
318    for _ in 0..n {
319        ctx.pop()?;
320    }
321
322    Ok(())
323}