• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Chik-Network / klvm_rs / 16770817670

06 Aug 2025 07:46AM UTC coverage: 93.355% (-0.3%) from 93.675%
16770817670

push

github

Chik-Network
update 0.16.0

526 of 581 new or added lines in 26 files covered. (90.53%)

1 existing line in 1 file now uncovered.

6294 of 6742 relevant lines covered (93.36%)

29671867.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.01
/src/run_program.rs
1
use super::traverse_path::{traverse_path, traverse_path_fast};
2
use crate::allocator::{Allocator, Checkpoint, NodePtr, NodeVisitor, SExp};
3
use crate::cost::Cost;
4
use crate::dialect::{Dialect, OperatorSet};
5
use crate::error::{EvalErr, Result};
6
use crate::op_utils::{first, get_args, uint_atom};
7
use crate::reduction::{Reduction, Response};
8

9
// lowered from 46
10
const QUOTE_COST: Cost = 20;
11
// lowered from 138
12
const APPLY_COST: Cost = 90;
13
// the cost of entering a softfork guard
14
const GUARD_COST: Cost = 140;
15
// mandatory base cost for every operator we execute
16
const OP_COST: Cost = 1;
17

18
// The max number of elements allowed on the stack. The program fails if this is
19
// exceeded
20
const STACK_SIZE_LIMIT: usize = 20000000;
21

22
#[cfg(feature = "pre-eval")]
23
pub type PreEval = Box<dyn Fn(&mut Allocator, NodePtr, NodePtr) -> Result<Option<Box<PostEval>>>>;
24

25
#[cfg(feature = "pre-eval")]
26
pub type PostEval = dyn Fn(&mut Allocator, Option<NodePtr>);
27

28
#[repr(u8)]
29
enum Operation {
30
    Apply,
31
    Cons,
32
    ExitGuard,
33
    SwapEval,
34

35
    #[cfg(feature = "pre-eval")]
36
    PostEval,
37
}
38

39
#[cfg(feature = "counters")]
40
#[derive(Debug)]
41
pub struct Counters {
42
    pub val_stack_usage: usize,
43
    pub env_stack_usage: usize,
44
    pub op_stack_usage: usize,
45
    pub atom_count: u32,
46
    pub small_atom_count: u32,
47
    pub pair_count: u32,
48
    pub heap_size: u32,
49
}
50

51
#[cfg(feature = "counters")]
52
impl Counters {
53
    fn new() -> Self {
494✔
54
        Counters {
494✔
55
            val_stack_usage: 0,
494✔
56
            env_stack_usage: 0,
494✔
57
            op_stack_usage: 0,
494✔
58
            atom_count: 0,
494✔
59
            small_atom_count: 0,
494✔
60
            pair_count: 0,
494✔
61
            heap_size: 0,
494✔
62
        }
494✔
63
    }
494✔
64
}
65

66
// this represents the state we were in before entering a soft-fork guard. We
67
// may need this to long-jump out of the guard, and also to validate the cost
68
// when exiting the guard
69
struct SoftforkGuard {
70
    // This is the expected cost of the program when exiting the guard. i.e. the
71
    // current_cost + the first argument to the operator
72
    expected_cost: Cost,
73

74
    // When exiting a softfork guard, all values used inside it are zapped. This
75
    // was the state of the allocator before entering. We restore to this state
76
    // on exit.
77
    allocator_state: Checkpoint,
78

79
    // this specifies which new operators are available
80
    operator_set: OperatorSet,
81

82
    #[cfg(test)]
83
    start_cost: Cost,
84
}
85

86
// `run_program` has three stacks:
87
// 1. the operand stack of `NodePtr` objects. val_stack
88
// 2. the operator stack of Operation. op_stack
89
// 3. the environment stack (points to the environment for the current
90
//    operation). env_stack
91

92
struct RunProgramContext<'a, D> {
93
    allocator: &'a mut Allocator,
94
    dialect: &'a D,
95
    val_stack: Vec<NodePtr>,
96
    env_stack: Vec<NodePtr>,
97
    op_stack: Vec<Operation>,
98
    softfork_stack: Vec<SoftforkGuard>,
99
    #[cfg(feature = "counters")]
100
    pub counters: Counters,
101

102
    #[cfg(feature = "pre-eval")]
103
    pre_eval: Option<PreEval>,
104
    #[cfg(feature = "pre-eval")]
105
    posteval_stack: Vec<Box<PostEval>>,
106
}
107

108
impl<'a, D: Dialect> RunProgramContext<'a, D> {
109
    #[cfg(feature = "counters")]
110
    #[inline(always)]
111
    fn account_val_push(&mut self) {
299,599✔
112
        self.counters.val_stack_usage =
299,599✔
113
            std::cmp::max(self.counters.val_stack_usage, self.val_stack.len());
299,599✔
114
    }
299,599✔
115

116
    #[cfg(feature = "counters")]
117
    #[inline(always)]
118
    fn account_env_push(&mut self) {
28,607✔
119
        self.counters.env_stack_usage =
28,607✔
120
            std::cmp::max(self.counters.env_stack_usage, self.env_stack.len());
28,607✔
121
    }
28,607✔
122

123
    #[cfg(feature = "counters")]
124
    #[inline(always)]
125
    fn account_op_push(&mut self) {
149,774✔
126
        self.counters.op_stack_usage =
149,774✔
127
            std::cmp::max(self.counters.op_stack_usage, self.op_stack.len());
149,774✔
128
    }
149,774✔
129

130
    #[cfg(not(feature = "counters"))]
131
    #[inline(always)]
132
    fn account_val_push(&mut self) {}
2,147,483,647✔
133

134
    #[cfg(not(feature = "counters"))]
135
    #[inline(always)]
136
    fn account_env_push(&mut self) {}
363,134,460✔
137

138
    #[cfg(not(feature = "counters"))]
139
    #[inline(always)]
140
    fn account_op_push(&mut self) {}
1,890,368,910✔
141

142
    pub fn pop(&mut self) -> Result<NodePtr> {
2,147,483,647✔
143
        let v: Option<NodePtr> = self.val_stack.pop();
2,147,483,647✔
144
        match v {
2,147,483,647✔
NEW
145
            None => Err(EvalErr::InternalError(
×
NEW
146
                NodePtr::NIL,
×
NEW
147
                "value stack empty".to_string(),
×
NEW
148
            ))?,
×
149
            Some(k) => Ok(k),
2,147,483,647✔
150
        }
151
    }
2,147,483,647✔
152
    pub fn push(&mut self, node: NodePtr) -> Result<()> {
2,147,483,647✔
153
        if self.val_stack.len() == STACK_SIZE_LIMIT {
2,147,483,647✔
NEW
154
            return Err(EvalErr::ValueStackLimitReached(node));
×
155
        }
2,147,483,647✔
156
        self.val_stack.push(node);
2,147,483,647✔
157
        self.account_val_push();
2,147,483,647✔
158
        Ok(())
2,147,483,647✔
159
    }
2,147,483,647✔
160

161
    pub fn push_env(&mut self, env: NodePtr) -> Result<()> {
363,163,067✔
162
        if self.env_stack.len() == STACK_SIZE_LIMIT {
363,163,067✔
NEW
163
            return Err(EvalErr::EnvironmentStackLimitReached(env));
×
164
        }
363,163,067✔
165
        self.env_stack.push(env);
363,163,067✔
166
        self.account_env_push();
363,163,067✔
167
        Ok(())
363,163,067✔
168
    }
363,163,067✔
169

170
    #[cfg(feature = "pre-eval")]
171
    fn new_with_pre_eval(
172
        allocator: &'a mut Allocator,
173
        dialect: &'a D,
174
        pre_eval: Option<PreEval>,
175
    ) -> Self {
176
        RunProgramContext {
177
            allocator,
178
            dialect,
179
            val_stack: Vec::new(),
180
            env_stack: Vec::new(),
181
            op_stack: Vec::new(),
182
            softfork_stack: Vec::new(),
183
            #[cfg(feature = "counters")]
184
            counters: Counters::new(),
185
            pre_eval,
186
            posteval_stack: Vec::new(),
187
        }
188
    }
189

190
    fn new(allocator: &'a mut Allocator, dialect: &'a D) -> Self {
564✔
191
        RunProgramContext {
564✔
192
            allocator,
564✔
193
            dialect,
564✔
194
            val_stack: Vec::new(),
564✔
195
            env_stack: Vec::new(),
564✔
196
            op_stack: Vec::new(),
564✔
197
            softfork_stack: Vec::new(),
564✔
198
            #[cfg(feature = "counters")]
564✔
199
            counters: Counters::new(),
564✔
200
            #[cfg(feature = "pre-eval")]
564✔
201
            pre_eval: None,
564✔
202
            #[cfg(feature = "pre-eval")]
564✔
203
            posteval_stack: Vec::new(),
564✔
204
        }
564✔
205
    }
564✔
206

207
    fn cons_op(&mut self) -> Result<Cost> {
754,745,610✔
208
        /* Join the top two operands. */
209
        let v1 = self.pop()?;
754,745,610✔
210
        let v2 = self.pop()?;
754,745,610✔
211
        let p = self.allocator.new_pair(v1, v2)?;
754,745,610✔
212
        self.push(p)?;
754,745,596✔
213
        Ok(0)
754,745,596✔
214
    }
754,745,610✔
215

216
    fn eval_op_atom(
506,750,045✔
217
        &mut self,
506,750,045✔
218
        operator_node: NodePtr,
506,750,045✔
219
        operand_list: NodePtr,
506,750,045✔
220
        env: NodePtr,
506,750,045✔
221
    ) -> Result<Cost> {
506,750,045✔
222
        // special case check for quote
223
        if self.allocator.small_number(operator_node) == Some(self.dialect.quote_kw()) {
506,750,045✔
224
            self.push(operand_list)?;
143,586,982✔
225
            Ok(QUOTE_COST)
143,586,982✔
226
        } else {
227
            self.push_env(env)?;
363,163,063✔
228
            self.op_stack.push(Operation::Apply);
363,163,063✔
229
            self.account_op_push();
363,163,063✔
230
            self.push(operator_node)?;
363,163,063✔
231
            let mut operands: NodePtr = operand_list;
363,163,063✔
232
            while let SExp::Pair(first, rest) = self.allocator.sexp(operands) {
1,129,817,115✔
233
                // We evaluate every entry in the argument list (using the
234
                // environment at the top of the env_stack) The resulting return
235
                // values are arranged in a list. the top item on the stack is
236
                // the resulting list, and below it is the next pair to
237
                // evaluated.
238
                //
239
                // each evaluation pops both, pushes the result list
240
                // back, evaluates and the executes the Cons operation
241
                // to add the most recent result to the list. Leaving
242
                // the new list at the top of the stack for the next
243
                // pair to be evaluated.
244
                self.op_stack.push(Operation::SwapEval);
766,654,052✔
245
                self.account_op_push();
766,654,052✔
246
                self.push(first)?;
766,654,052✔
247
                operands = rest;
766,654,052✔
248
            }
249
            // ensure a correct nil terminator
250
            if self.allocator.atom_len(operands) != 0 {
363,163,063✔
NEW
251
                Err(EvalErr::InvalidNilTerminator(operand_list))
×
252
            } else {
253
                self.push(self.allocator.nil())?;
363,163,063✔
254
                Ok(OP_COST)
363,163,063✔
255
            }
256
        }
257
    }
506,750,045✔
258

259
    fn eval_pair(&mut self, program: NodePtr, env: NodePtr) -> Result<Cost> {
841,411,327✔
260
        #[cfg(feature = "pre-eval")]
261
        if let Some(pre_eval) = &self.pre_eval {
262
            if let Some(post_eval) = pre_eval(self.allocator, program, env)? {
263
                self.posteval_stack.push(post_eval);
264
                self.op_stack.push(Operation::PostEval);
265
            }
266
        };
267

268
        // put a bunch of ops on op_stack
269
        let SExp::Pair(op_node, op_list) = self.allocator.sexp(program) else {
841,411,327✔
270
            // the program is just a bitfield path through the env tree
271
            let r = match self.allocator.node(program) {
334,661,277✔
272
                NodeVisitor::Buffer(buf) => traverse_path(self.allocator, buf, env)?,
6✔
273
                NodeVisitor::U32(val) => traverse_path_fast(self.allocator, val, env)?,
334,661,271✔
274
                NodeVisitor::Pair(_, _) => {
NEW
275
                    return Err(EvalErr::InvalidOpArg(
×
NEW
276
                        program,
×
NEW
277
                        "expected atom, got pair".to_string(),
×
NEW
278
                    ))?;
×
279
                }
280
            };
281
            self.push(r.1)?;
334,661,276✔
282
            return Ok(r.0);
334,661,276✔
283
        };
284

285
        match self.allocator.sexp(op_node) {
506,750,050✔
286
            SExp::Pair(new_operator, _) => {
5✔
287
                let [inner] = get_args::<1>(
5✔
288
                    self.allocator,
5✔
289
                    op_node,
5✔
290
                    "in the ((X)...) syntax, the inner list",
5✔
291
                )?;
1✔
292
                if let SExp::Pair(_, _) = self.allocator.sexp(inner) {
4✔
NEW
293
                    return Err(EvalErr::InvalidOpArg(
×
NEW
294
                        program,
×
NEW
295
                        "in ((X)...) syntax X must be lone atom".to_string(),
×
NEW
296
                    ));
×
297
                }
4✔
298
                self.push_env(env)?;
4✔
299
                self.push(new_operator)?;
4✔
300
                self.push(op_list)?;
4✔
301
                self.op_stack.push(Operation::Apply);
4✔
302
                self.account_op_push();
4✔
303
                Ok(APPLY_COST)
4✔
304
            }
305
            SExp::Atom => self.eval_op_atom(op_node, op_list, env),
506,750,045✔
306
        }
307
    }
841,411,327✔
308

309
    fn swap_eval_op(&mut self) -> Result<Cost> {
760,701,565✔
310
        let v2 = self.pop()?;
760,701,565✔
311
        let program: NodePtr = self.pop()?;
760,701,565✔
312
        let env: NodePtr = *self.env_stack.last().ok_or(EvalErr::InternalError(
760,701,565✔
313
            program,
760,701,565✔
314
            "environment stack empty".to_string(),
760,701,565✔
315
        ))?;
760,701,565✔
316
        self.push(v2)?;
760,701,565✔
317

318
        // on the way back, build a list from the values
319
        self.op_stack.push(Operation::Cons);
760,701,565✔
320
        self.account_op_push();
760,701,565✔
321

322
        self.eval_pair(program, env)
760,701,565✔
323
    }
760,701,565✔
324

325
    fn parse_softfork_arguments(&self, args: NodePtr) -> Result<(OperatorSet, NodePtr, NodePtr)> {
114✔
326
        let [_cost, extension, program, env] = get_args::<4>(self.allocator, args, "softfork")?;
114✔
327

328
        let extension =
98✔
329
            self.dialect
104✔
330
                .softfork_extension(uint_atom::<4>(self.allocator, extension, "softfork")? as u32);
104✔
331
        if extension == OperatorSet::Default {
98✔
332
            Err(EvalErr::UnknownSoftforkExtension)
34✔
333
        } else {
334
            Ok((extension, program, env))
64✔
335
        }
336
    }
114✔
337

338
    fn apply_op(&mut self, current_cost: Cost, max_cost: Cost) -> Result<Cost> {
357,207,098✔
339
        let operand_list = self.pop()?;
357,207,098✔
340
        let operator = self.pop()?;
357,207,098✔
341
        if self.env_stack.pop().is_none() {
357,207,098✔
NEW
342
            return Err(EvalErr::InternalError(
×
NEW
343
                operator,
×
NEW
344
                "environment stack empty".to_string(),
×
NEW
345
            ));
×
346
        }
357,207,098✔
347
        let op_atom = self.allocator.small_number(operator);
357,207,098✔
348

349
        if op_atom == Some(self.dialect.apply_kw()) {
357,207,098✔
350
            let [new_operator, env] = get_args::<2>(self.allocator, operand_list, "apply")?;
80,709,136✔
351
            self.eval_pair(new_operator, env).map(|c| c + APPLY_COST)
80,709,134✔
352
        } else if op_atom == Some(self.dialect.softfork_kw()) {
276,497,962✔
353
            let expected_cost = uint_atom::<8>(
171✔
354
                self.allocator,
172✔
355
                first(self.allocator, operand_list)?,
172✔
356
                "softfork",
171✔
357
            )?;
2✔
358
            if expected_cost > max_cost {
169✔
359
                return Err(EvalErr::CostExceeded);
54✔
360
            }
115✔
361
            if expected_cost == 0 {
115✔
362
                return Err(EvalErr::CostExceeded);
1✔
363
            }
114✔
364

365
            // we can't blindly propagate errors here, since we handle errors
366
            // differently depending on whether we allow unknown ops or not
367
            let (ext, prg, env) = match self.parse_softfork_arguments(operand_list) {
114✔
368
                Ok(ret_values) => ret_values,
64✔
369
                Err(err) => {
50✔
370
                    if self.dialect.allow_unknown_ops() {
50✔
371
                        // In this case, we encountered a softfork invocation
372
                        // that doesn't pass the correct arguments.
373
                        // if we're in consensus mode, we have to accept this as
374
                        // something we don't understand
375
                        self.push(self.allocator.nil())?;
27✔
376
                        return Ok(expected_cost);
27✔
377
                    }
23✔
378
                    return Err(err);
23✔
379
                }
380
            };
381

382
            self.softfork_stack.push(SoftforkGuard {
64✔
383
                expected_cost: current_cost + expected_cost,
64✔
384
                allocator_state: self.allocator.checkpoint(),
64✔
385
                operator_set: ext,
64✔
386
                #[cfg(test)]
64✔
387
                start_cost: current_cost,
64✔
388
            });
64✔
389

390
            // once the softfork guard exits, we need to ensure the cost that was
391
            // specified match the true cost. We also free heap allocations
392
            self.op_stack.push(Operation::ExitGuard);
64✔
393

394
            self.eval_pair(prg, env).map(|c| c + GUARD_COST)
64✔
395
        } else {
396
            let current_extensions = if let Some(sf) = self.softfork_stack.last() {
276,497,790✔
397
                sf.operator_set
189✔
398
            } else {
399
                OperatorSet::Default
276,497,601✔
400
            };
401

402
            let r = self.dialect.op(
276,497,790✔
403
                self.allocator,
276,497,790✔
404
                operator,
276,497,790✔
405
                operand_list,
276,497,790✔
406
                max_cost,
276,497,790✔
407
                current_extensions,
276,497,790✔
408
            )?;
153✔
409
            self.push(r.1)?;
276,497,637✔
410
            Ok(r.0)
276,497,637✔
411
        }
412
    }
357,207,098✔
413

414
    fn exit_guard(&mut self, current_cost: Cost) -> Result<Cost> {
27✔
415
        // this is called when we are done executing a softfork program.
416
        // This is when we have to validate the cost
417
        let guard = self
27✔
418
            .softfork_stack
27✔
419
            .pop()
27✔
420
            .expect("internal error. exiting a softfork that's already been popped");
27✔
421

422
        if current_cost != guard.expected_cost {
27✔
423
            #[cfg(test)]
424
            println!(
1✔
425
                "actual cost: {} specified cost: {}",
1✔
426
                current_cost - guard.start_cost,
1✔
427
                guard.expected_cost - guard.start_cost
1✔
428
            );
429
            return Err(EvalErr::SoftforkCostMismatch);
1✔
430
        }
26✔
431

432
        // restore the allocator to the state when we entered the softfork guard
433
        // This is an optimization to reclaim all heap space allocated by the
434
        // softfork program. Since the softfork always return nil, no value can
435
        // escape the softfork program, and it's therefore safe to restore the
436
        // heap
437
        self.allocator.restore_checkpoint(&guard.allocator_state);
26✔
438

439
        // the softfork always returns nil, pop the value pushed by the
440
        // evaluation of the program and push nil instead
441
        self.pop()
26✔
442
            .expect("internal error, softfork program did not push value onto stack");
26✔
443

444
        self.push(self.allocator.nil())?;
26✔
445

446
        Ok(0)
26✔
447
    }
27✔
448

449
    pub fn run_program(&mut self, program: NodePtr, env: NodePtr, max_cost: Cost) -> Response {
564✔
450
        self.val_stack = vec![];
564✔
451
        self.op_stack = vec![];
564✔
452

453
        // max_cost is always in effect, and necessary to prevent wrap-around of
454
        // the cost integer.
455
        let max_cost = if max_cost == 0 { Cost::MAX } else { max_cost };
564✔
456
        // We would previously allocate an atom to hold the max cost for the program.
457
        // Since we don't anymore we need to increment the ghost atom counter to remain
458
        // backwards compatible with the atom count limit
459
        self.allocator.add_ghost_atom(1)?;
564✔
460
        let mut cost: Cost = 0;
564✔
461

462
        cost += self.eval_pair(program, env)?;
564✔
463

464
        loop {
465
            // if we are in a softfork guard, temporarily use the guard's
466
            // expected cost as the upper limit. This lets us fail early in case
467
            // it's wrong. It's guaranteed to be <= max_cost, because we check
468
            // that when entering the softfork guard
469
            let effective_max_cost = if let Some(sf) = self.softfork_stack.last() {
1,872,654,611✔
470
                sf.expected_cost
1,338✔
471
            } else {
472
                max_cost
1,872,653,273✔
473
            };
474

475
            if cost > effective_max_cost {
1,872,654,611✔
476
                return Err(EvalErr::CostExceeded);
150✔
477
            }
1,872,654,461✔
478
            let top = self.op_stack.pop();
1,872,654,461✔
479
            let op = match top {
1,872,654,461✔
480
                Some(f) => f,
1,872,654,300✔
481
                None => break,
161✔
482
            };
483
            cost += match op {
1,872,654,300✔
484
                Operation::Apply => self.apply_op(cost, effective_max_cost - cost)?,
357,207,098✔
485
                Operation::ExitGuard => self.exit_guard(cost)?,
27✔
486
                Operation::Cons => self.cons_op()?,
754,745,610✔
487
                Operation::SwapEval => self.swap_eval_op()?,
760,701,565✔
488
                #[cfg(feature = "pre-eval")]
489
                Operation::PostEval => {
490
                    let f = self.posteval_stack.pop().unwrap();
491
                    let peek: Option<NodePtr> = self.val_stack.last().copied();
492
                    f(self.allocator, peek);
493
                    0
494
                }
495
            };
496
        }
497
        Ok(Reduction(cost, self.pop()?))
161✔
498
    }
564✔
499
}
500

501
pub fn run_program<'a, D: Dialect>(
563✔
502
    allocator: &'a mut Allocator,
563✔
503
    dialect: &'a D,
563✔
504
    program: NodePtr,
563✔
505
    env: NodePtr,
563✔
506
    max_cost: Cost,
563✔
507
) -> Response {
563✔
508
    let mut rpc = RunProgramContext::new(allocator, dialect);
563✔
509
    rpc.run_program(program, env, max_cost)
563✔
510
}
563✔
511

512
#[cfg(feature = "pre-eval")]
513
pub fn run_program_with_pre_eval<'a, D: Dialect>(
514
    allocator: &'a mut Allocator,
515
    dialect: &'a D,
516
    program: NodePtr,
517
    env: NodePtr,
518
    max_cost: Cost,
519
    pre_eval: Option<PreEval>,
520
) -> Response {
521
    let mut rpc = RunProgramContext::new_with_pre_eval(allocator, dialect, pre_eval);
522
    rpc.run_program(program, env, max_cost)
523
}
524

525
#[cfg(feature = "counters")]
526
pub fn run_program_with_counters<'a, D: Dialect>(
1✔
527
    allocator: &'a mut Allocator,
1✔
528
    dialect: &'a D,
1✔
529
    program: NodePtr,
1✔
530
    env: NodePtr,
1✔
531
    max_cost: Cost,
1✔
532
) -> (Counters, Response) {
1✔
533
    let mut rpc = RunProgramContext::new(allocator, dialect);
1✔
534
    let ret = rpc.run_program(program, env, max_cost);
1✔
535
    rpc.counters.atom_count = rpc.allocator.atom_count() as u32;
1✔
536
    rpc.counters.small_atom_count = rpc.allocator.small_atom_count() as u32;
1✔
537
    rpc.counters.pair_count = rpc.allocator.pair_count() as u32;
1✔
538
    rpc.counters.heap_size = rpc.allocator.heap_size() as u32;
1✔
539
    (rpc.counters, ret)
1✔
540
}
1✔
541

542
#[cfg(test)]
543
mod tests {
544
    use super::*;
545

546
    use crate::chik_dialect::{ENABLE_KECCAK_OPS_OUTSIDE_GUARD, NO_UNKNOWN_OPS};
547
    use crate::test_ops::parse_exp;
548

549
    use rstest::rstest;
550

551
    struct RunProgramTest<'a> {
552
        prg: &'a str,
553
        args: &'a str,
554
        flags: u32,
555
        result: Option<&'a str>,
556
        cost: Cost,
557
        err: &'a str,
558
    }
559

560
    const TEST_CASES: &[RunProgramTest] = &[
561
        RunProgramTest {
562
            prg: "(/ (q . 10) (q . -3))",
563
            args: "()",
564
            flags: 0,
565
            result: Some("-4"),
566
            cost: 1047,
567
            err: "",
568
        },
569
        RunProgramTest {
570
            prg: "(/ (q . -10) (q . 3))",
571
            args: "()",
572
            flags: 0,
573
            result: Some("-4"),
574
            cost: 1047,
575
            err: "",
576
        },
577
        RunProgramTest {
578
            prg: "(/ (q . -1) (q . 2))",
579
            args: "()",
580
            flags: 0,
581
            result: Some("-1"),
582
            cost: 1047,
583
            err: "",
584
        },
585
        // (mod (X N) (defun power (X N) (if (= N 0) 1 (* X (power X (- N 1))))) (power X N))
586
        RunProgramTest {
587
            prg: "(a (q 2 2 (c 2 (c 5 (c 11 ())))) (c (q 2 (i (= 11 ()) (q 1 . 1) (q 18 5 (a 2 (c 2 (c 5 (c (- 11 (q . 1)) ())))))) 1) 1))",
588
            args: "(5033 1000)",
589
            flags: 0,
590
            result: Some("0x024d4f505f1f813ca5e0ae8805bad8707347e65c5f7595da4852be5074288431d1df11a0c326d249f1f52ee051579403d1d0c23a7a1e9af18b7d7dc4c63c73542863c434ae9dfa80141a30cf4acee0d6c896aa2e64ea748404427a3bdaa1b97e4e09b8f5e4f8e9c568a4fc219532dbbad5ec54476d19b7408f8e7e7df16b830c20a1e83d90cc0620b0677b7606307f725539ef223561cdb276baf8e92156ee6492d97159c8f64768349ea7e219fd07fa818a59d81d0563b140396402f0ff758840da19808440e0a57c94c48ef84b4ab7ca8c5f010b69b8f443b12b50bd91bdcf2a96208ddac283fa294d6a99f369d57ab41d03eab5bb4809223c141ad94378516e6766a5054e22e997e260978af68a86893890d612f081b40d54fd1e940af35c0d7900c9a917e2458a61ef8a83f7211f519b2c5f015dfa7c2949ef8bedd02d3bad64ca9b2963dc2bb79f24092331133a7a299872079b9d0422b8fc0eeba4e12c7667ac7282cc6ff98a7c670614c9fce5a061b8d5cd4dd3c6d62d245688b62f9713dc2604bdd5bbc85c070c51f784a9ebac0e0eaa2e29e82d93e570887aa7e1a9d25baf0b2c55a4615f35ec0dbe9baa921569700f95e10cd2d4f6ba152a2ac288c37b60980df33dadfa920fd43dbbf55a0b333b88a3237d954e33d80ed6582019faf51db5f1b52e392559323f8bdd945e7fc6cb8f97f2b8417cfc184d7bfbfa5314d4114f95b725847523f1848d13c28ad96662298ee4e2d87af23e7cb4e58d7a20a5c57ae6833b4a37dcafccca0245a0d6ef28f83200d74db390281e03dd3a8b782970895764c3fcef31c5ed6d0b6e4e796a62ad5654691eea0d9db351cc4fee63248405b24c98bd5e68e4a5e0ab11e90e3c7de270c594d3a35639d931853b7010c8c896f6b28b2af719e53da65da89d44b926b6f06123c9217a43be35d751516bd02c18c4f868a2eae78ae3c6deab1115086c8ce58414db4561865d17ab95c7b3d4e1bfc6d0a4d3fbf5f20a0a7d77a9270e4da354c588da55b0063aec76654019ffb310e1503d99a7bc81ccdf5f8b15c8638156038624cf35988d8420bfdb59184c4b86bf5448df65c44aedc2e98eead7f1ba4be8f402baf12d41076b8f0991cfc778e04ba2c05d1440c70488ffaeefde537064035037f729b683e8ff1b3d0b4aa26a2b30bcaa9379f7fcc7072ff9a2c3e801c5979b0ab3e7acf89373de642d596f26514b9fa213ca217181a8429ad69d14445a822b16818c2509480576dc0ff7bac48c557e6d1883039f4daf873fa4f9a4d849130e2e4336049cfaf9e69a7664f0202b901cf07c7065c4dc93c46f98c5ea5c9c9d911b733093490da3bf1c95f43cd18b7be3798535a55ac6da3442946a268b74bde1349ca9807c41d90c7ec218a17efd2c21d5fcd720501f8a488f1dfba0a423dfdb2a877707b77930e80d734ceabcdb24513fad8f2e2470604d041df083bf184edd0e9720dd2b608b1ee1df951d7ce8ec671317b4f5a3946aa75280658b4ef77b3f504ce73e7ecac84eec3c2b45fb62f6fbd5ab78c744abd3bf5d0ab37d7b19124d2470d53db09ddc1f9dd9654b0e6a3a44c95d0a5a5e061bd24813508d3d1c901544dc3e6b84ca38dd2fde5ea60a57cbc12428848c4e3f6fd4941ebd23d709a717a090dd01830436659f7c20fd2d70c916427e9f3f12ac479128c2783f02a9824aa4e31de133c2704e049a50160f656e28aa0a2615b32bd48bb5d5d13d363a487324c1e9b8703be938bc545654465c9282ad5420978263b3e3ba1bb45e1a382554ac68e5a154b896c9c4c2c3853fbbfc877c4fb7dc164cc420f835c413839481b1d2913a68d206e711fb19b284a7bb2bd2033531647cf135833a0f3026b0c1dc0c184120d30ef4865985fdacdfb848ab963d2ae26a784b7b6a64fdb8feacf94febed72dcd0a41dc12be26ed79af88f1d9cba36ed1f95f2da8e6194800469091d2dfc7b04cfe93ab7a7a888b2695bca45a76a1458d08c3b6176ab89e7edc56c7e01142adfff944641b89cd5703a911145ac4ec42164d90b6fcd78b39602398edcd1f935485894fb8a1f416e031624806f02fbd07f398dbfdd48b86dfacf2045f85ecfe5bb1f01fae758dcdb4ae3b1e2aac6f0878f700d1f430b8ca47c9d8254059bd5c006042c4605f33ca98b41"),
591
            cost: 15073165,
592
            err: "",
593
        },
594
        // '
595
        RunProgramTest {
596
            prg: "(= (point_add (pubkey_for_exp (q . -2)) (pubkey_for_exp (q . 5))) (pubkey_for_exp (q . 3)))",
597
            args: "()",
598
            flags: 0,
599
            result: Some("1"),
600
            cost: 6768556,
601
            err: "",
602
        },
603
        RunProgramTest {
604
            prg: "(= (point_add (pubkey_for_exp (q . 2)) (pubkey_for_exp (q . 3))) (pubkey_for_exp (q . 5)))",
605
            args: "()",
606
            flags: 0,
607
            result: Some("1"),
608
            cost: 6768556,
609
            err: "",
610
        },
611
        RunProgramTest {
612
            prg: "(point_add (pubkey_for_exp (q . 1)) (pubkey_for_exp (q . 2)))",
613
            args: "()",
614
            flags: 0,
615
            result: Some("0x89ece308f9d1f0131765212deca99697b112d61f9be9a5f1f3780a51335b3ff981747a0b2ca2179b96d2c0c9024e5224"),
616
            cost: 5442073,
617
            err: "",
618
        },
619
        RunProgramTest {
620
            prg: "(f (f (q . ((100 200 300) 400 500))))",
621
            args: "()",
622
            flags: 0,
623
            result: Some("0x64"),
624
            cost: 82,
625
            err: "",
626
        },
627
        RunProgramTest {
628
            prg: "(= (f 1) (+ (f (r 1)) (f (r (r 1)))))",
629
            args: "(7 3 3)",
630
            flags: 0,
631
            result: Some("()"),
632
            cost: 1194,
633
            err: "",
634
        },
635
        RunProgramTest {
636
            prg: "(= (f 1) (+ (f (r 1)) (f (r (r 1)))))",
637
            args: "(7 3 4)",
638
            flags: 0,
639
            result: Some("1"),
640
            cost: 1194,
641
            err: "",
642
        },
643
        RunProgramTest {
644
            prg: "(i (f (r (r 1))) (f 1) (f (r 1)))",
645
            args: "(200 300 400)",
646
            flags: 0,
647
            result: Some("0x00c8"),
648
            cost: 352,
649
            err: "",
650
        },
651
        RunProgramTest {
652
            prg: "(i (f (r (r 1))) (f 1) (f (r 1)))",
653
            args: "(200 300 1)",
654
            flags: 0,
655
            result: Some("0x00c8"),
656
            cost: 352,
657
            err: "",
658
        },
659
        RunProgramTest {
660
            prg: "(r (r (q . ((100 200 300) 400 500))))",
661
            args: "()",
662
            flags: 0,
663
            result: Some("(500)"),
664
            cost: 82,
665
            err: "",
666
        },
667
        RunProgramTest {
668
            prg: "(* (q . 10000000000000000000000000000000000) (q . 10000000000000000000000000000000) (q . 100000000000000000000000000000000000000) (q . 1000000000000000000000000000000) (q . 1000000000000000000000000000000) (q . 1000000000000000000000000000000) (q . 1000000000000000000000000000000) (q . 1000000000000000000000000000000) (q . 1000000000000000000000000000000) (q . 1000000000000000000000000000000) (q . 1000000000000000000000000000000) (q . 1000000000000000000000000000000) (q . 1000000000000000000000000000000) (q . 1000000000000000000000000000000) (q . 1000000000000000000000000000000))",
669
            args: "()",
670
            flags: 0,
671
            result: Some("0x04261a5c969abab851babdb4f178e63bf2ed3879fc13a4c75622d73c909440a4763849b52e49cd2522500f555f6a3131775f93ddcf24eda7a1dbdf828a033626da873caaaa880a9121f4c44a157973f60443dc53bc99ac12d5bd5fa20a88320ae2ccb8e1b5e792cbf0d001bb0fbd7765d3936e412e2fc8f1267833237237fcb638dda0a7aa674680000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
672
            cost: 24255,
673
            err: "",
674
        },
675

676
        // ## APPLY
677
        RunProgramTest {
678
            prg: "(a (q 0x0fffffffff) (q ()))",
679
            args: "()",
680
            flags: 0,
681
            result: None,
682
            cost: 0,
683
            err: "invalid operator",
684
        },
685
        RunProgramTest {
686
            prg: "(a (q . 0) (q . 1) (q . 2))",
687
            args: "()",
688
            flags: 0,
689
            result: None,
690
            cost: 0,
691
            err: "InvalidOperatorArg: apply takes exactly 2 argument(s)",
692
        },
693
        RunProgramTest {
694
            prg: "(a (q 0x00ffffffffffffffffffff00) (q ()))",
695
            args: "()",
696
            flags: 0,
697
            result: None,
698
            cost: 0,
699
            err: "invalid operator",
700
        },
701
        RunProgramTest {
702
            prg: "(a (q . 1))",
703
            args: "()",
704
            flags: 0,
705
            result: None,
706
            cost: 0,
707
            err: "InvalidOperatorArg: apply takes exactly 2 argument(s)",
708
        },
709
        RunProgramTest {
710
            prg: "(a (q . 1) (q . (100 200)))",
711
            args: "()",
712
            flags: 0,
713
            result: Some("(100 200)"),
714
            cost: 175,
715
            err: "",
716
        },
717
        RunProgramTest {
718
            prg: "(a (q . (+ 2 5)) (q . (20 30)))",
719
            args: "()",
720
            flags: 0,
721
            result: Some("50"),
722
            cost: 987,
723
            err: "",
724
        },
725
        RunProgramTest {
726
            prg: "((c (q . (+ (q . 50) 1)) (q . 500)))",
727
            args: "()",
728
            flags: 0,
729
            result: None,
730
            cost: 0,
731
            err: "InvalidOperatorArg: in the ((X)...) syntax, the inner list takes exactly 1 argument(s)",
732
        },
733
        RunProgramTest {
734
            prg: "((#c) (q . 3) (q . 4))",
735
            args: "()",
736
            flags: 0,
737
            result: Some("((1 . 3) 1 . 4)"),
738
            cost: 140,
739
            err: "",
740
        },
741
        RunProgramTest {
742
            prg: "((#+) 1 2 3)",
743
            args: "()",
744
            flags: 0,
745
            result: Some("6"),
746
            cost: 1168,
747
            err: "",
748
        },
749
        RunProgramTest {
750
            prg: "(a (q . 2) (q . (3 4 5)))",
751
            args: "()",
752
            flags: 0,
753
            result: Some("3"),
754
            cost: 179,
755
            err: "",
756
        },
757

758
        // ## PATH LOOKUPS
759

760
        // 0
761
        RunProgramTest {
762
            prg: "0",
763
            args: "(((8 . 12) . (10 . 14)) . ((9 . 13) . (11 . 15)))",
764
            flags: 0,
765
            result: Some("()"),
766
            cost: 44,
767
            err: "",
768
        },
769
        // 1
770
        RunProgramTest {
771
            prg: "1",
772
            args: "(((8 . 12) . (10 . 14)) . ((9 . 13) . (11 . 15)))",
773
            flags: 0,
774
            result: Some("(((8 . 12) 10 . 14) (9 . 13) 11 . 15)"),
775
            cost: 44,
776
            err: "",
777
        },
778
        // 2
779
        RunProgramTest {
780
            prg: "2",
781
            args: "(((8 . 12) . (10 . 14)) . ((9 . 13) . (11 . 15)))",
782
            flags: 0,
783
            result: Some("((8 . 12) 10 . 14)"),
784
            cost: 48,
785
            err: "",
786
        },
787
        // 3
788
        RunProgramTest {
789
            prg: "3",
790
            args: "(((8 . 12) . (10 . 14)) . ((9 . 13) . (11 . 15)))",
791
            flags: 0,
792
            result: Some("((9 . 13) 11 . 15)"),
793
            cost: 48,
794
            err: "",
795
        },
796
        // 4
797
        RunProgramTest {
798
            prg: "4",
799
            args: "(((8 . 12) . (10 . 14)) . ((9 . 13) . (11 . 15)))",
800
            flags: 0,
801
            result: Some("(8 . 12)"),
802
            cost: 52,
803
            err: "",
804
        },
805
        // 5
806
        RunProgramTest {
807
            prg: "5",
808
            args: "(((8 . 12) . (10 . 14)) . ((9 . 13) . (11 . 15)))",
809
            flags: 0,
810
            result: Some("(9 . 13)"),
811
            cost: 52,
812
            err: "",
813
        },
814
        // 6
815
        RunProgramTest {
816
            prg: "6",
817
            args: "(((8 . 12) . (10 . 14)) . ((9 . 13) . (11 . 15)))",
818
            flags: 0,
819
            result: Some("(10 . 14)"),
820
            cost: 52,
821
            err: "",
822
        },
823
        // 7
824
        RunProgramTest {
825
            prg: "7",
826
            args: "(((8 . 12) . (10 . 14)) . ((9 . 13) . (11 . 15)))",
827
            flags: 0,
828
            result: Some("(11 . 15)"),
829
            cost: 52,
830
            err: "",
831
        },
832
        RunProgramTest {
833
            prg: "8",
834
            args: "(((8 . 12) . (10 . 14)) . ((9 . 13) . (11 . 15)))",
835
            flags: 0,
836
            result: Some("8"),
837
            cost: 56,
838
            err: "",
839
        },
840
        RunProgramTest {
841
            prg: "9",
842
            args: "(((8 . 12) . (10 . 14)) . ((9 . 13) . (11 . 15)))",
843
            flags: 0,
844
            result: Some("9"),
845
            cost: 56,
846
            err: "",
847
        },
848
        RunProgramTest {
849
            prg: "10",
850
            args: "(((8 . 12) . (10 . 14)) . ((9 . 13) . (11 . 15)))",
851
            flags: 0,
852
            result: Some("10"),
853
            cost: 56,
854
            err: "",
855
        },
856
        RunProgramTest {
857
            prg: "11",
858
            args: "(((8 . 12) . (10 . 14)) . ((9 . 13) . (11 . 15)))",
859
            flags: 0,
860
            result: Some("11"),
861
            cost: 56,
862
            err: "",
863
        },
864
        RunProgramTest {
865
            prg: "12",
866
            args: "(((8 . 12) . (10 . 14)) . ((9 . 13) . (11 . 15)))",
867
            flags: 0,
868
            result: Some("12"),
869
            cost: 56,
870
            err: "",
871
        },
872
        RunProgramTest {
873
            prg: "13",
874
            args: "(((8 . 12) . (10 . 14)) . ((9 . 13) . (11 . 15)))",
875
            flags: 0,
876
            result: Some("13"),
877
            cost: 56,
878
            err: "",
879
        },
880
        RunProgramTest {
881
            prg: "14",
882
            args: "(((8 . 12) . (10 . 14)) . ((9 . 13) . (11 . 15)))",
883
            flags: 0,
884
            result: Some("14"),
885
            cost: 56,
886
            err: "",
887
        },
888
        RunProgramTest {
889
            prg: "15",
890
            args: "(((8 . 12) . (10 . 14)) . ((9 . 13) . (11 . 15)))",
891
            flags: 0,
892
            result: Some("15"),
893
            cost: 56,
894
            err: "",
895
        },
896
        RunProgramTest {
897
            prg: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001",
898
            args: "(((0x1337 . (0x1337 . (42 . 0x1337))) . 0x1337) . 0x1337)",
899
            flags: 0,
900
            result: Some("(((0x1337 . (0x1337 . (42 . 0x1337))) . 0x1337) . 0x1337)"),
901
            cost: 536,
902
            err: "",
903
        },
904
        RunProgramTest {
905
            prg: "0x0000C8C141AB3121E776",
906
            args: "((0x1337 . (0x1337 . ((0x1337 . (0x1337 . (0x1337 . ((0x1337 . (0x1337 . (0x1337 . (((0x1337 . (0x1337 . (0x1337 . (0x1337 . (((((0x1337 . (((0x1337 . ((((0x1337 . (0x1337 . (((0x1337 . (0x1337 . ((0x1337 . ((0x1337 . ((0x1337 . (0x1337 . ((((((0x1337 . ((0x1337 . ((((((0x1337 . (0x1337 . ((((0x1337 . (((0x1337 . 42) . 0x1337) . 0x1337)) . 0x1337) . 0x1337) . 0x1337))) . 0x1337) . 0x1337) . 0x1337) . 0x1337) . 0x1337)) . 0x1337)) . 0x1337) . 0x1337) . 0x1337) . 0x1337) . 0x1337))) . 0x1337)) . 0x1337)) . 0x1337))) . 0x1337) . 0x1337))) . 0x1337) . 0x1337) . 0x1337)) . 0x1337) . 0x1337)) . 0x1337) . 0x1337) . 0x1337) . 0x1337))))) . 0x1337) . 0x1337)))) . 0x1337)))) . 0x1337))) . 0x1337)",
907
            flags: 0,
908
            result: Some("42"),
909
            cost: 304,
910
            err: "",
911
        },
912
        RunProgramTest {
913
            prg: "7708975405620101644641102810267383005",
914
            args: "(0x1337 . ((0x1337 . (0x1337 . (0x1337 . ((0x1337 . (0x1337 . (((0x1337 . ((0x1337 . (0x1337 . (0x1337 . (0x1337 . (0x1337 . ((0x1337 . (0x1337 . ((0x1337 . (((0x1337 . (0x1337 . (0x1337 . ((0x1337 . (((0x1337 . (((0x1337 . (0x1337 . (0x1337 . (0x1337 . ((0x1337 . ((0x1337 . (((((0x1337 . ((0x1337 . ((0x1337 . (0x1337 . (0x1337 . (((0x1337 . (0x1337 . ((0x1337 . (0x1337 . ((((0x1337 . (0x1337 . (0x1337 . (0x1337 . (((((0x1337 . (0x1337 . (0x1337 . (0x1337 . (0x1337 . (((((0x1337 . (((((0x1337 . ((0x1337 . (0x1337 . ((((0x1337 . ((((0x1337 . ((0x1337 . ((0x1337 . ((0x1337 . (0x1337 . (0x1337 . ((((0x1337 . (0x1337 . ((0x1337 . (((0x1337 . (0x1337 . (((0x1337 . (0x1337 . (0x1337 . (42 . 0x1337)))) . 0x1337) . 0x1337))) . 0x1337) . 0x1337)) . 0x1337))) . 0x1337) . 0x1337) . 0x1337)))) . 0x1337)) . 0x1337)) . 0x1337)) . 0x1337) . 0x1337) . 0x1337)) . 0x1337) . 0x1337) . 0x1337))) . 0x1337)) . 0x1337) . 0x1337) . 0x1337) . 0x1337)) . 0x1337) . 0x1337) . 0x1337) . 0x1337)))))) . 0x1337) . 0x1337) . 0x1337) . 0x1337))))) . 0x1337) . 0x1337) . 0x1337))) . 0x1337))) . 0x1337) . 0x1337)))) . 0x1337)) . 0x1337)) . 0x1337) . 0x1337) . 0x1337) . 0x1337)) . 0x1337)) . 0x1337))))) . 0x1337) . 0x1337)) . 0x1337) . 0x1337)) . 0x1337)))) . 0x1337) . 0x1337)) . 0x1337))) . 0x1337)))))) . 0x1337)) . 0x1337) . 0x1337))) . 0x1337)))) . 0x1337))",
915
            flags: 0,
916
            result: Some("42"),
917
            cost: 532,
918
            err: "",
919
        },
920
        RunProgramTest {
921
            prg: "1",
922
            args: "1",
923
            flags: 0,
924
            result: Some("1"),
925
            cost: 44,
926
            err: "",
927
        },
928
        RunProgramTest {
929
            prg: "(> 3 3)",
930
            args: "()",
931
            flags: 0,
932
            result: None,
933
            cost: 0,
934
            err: "path into atom",
935
        },
936

937
        // ## SOFTFORK
938

939
        // the arguments to softfork are checked in mempool mode, but in consensus
940
        // mode, only the cost argument is
941
        RunProgramTest {
942
            prg: "(softfork (q . 979))",
943
            args: "()",
944
            flags: 0,
945
            result: Some("()"),
946
            cost: 1000,
947
            err: "",
948
        },
949
        RunProgramTest {
950
            prg: "(softfork (q . 979))",
951
            args: "()",
952
            flags: NO_UNKNOWN_OPS,
953
            result: None,
954
            cost: 1000,
955
            err: "InvalidOperatorArg: softfork takes exactly 4 argument(s)",
956
        },
957
        RunProgramTest {
958
            prg: "(softfork (q . 959) (q . 9))",
959
            args: "()",
960
            flags: 0,
961
            result: Some("()"),
962
            cost: 1000,
963
            err: "",
964
        },
965
        RunProgramTest {
966
            prg: "(softfork (q . 959) (q . 9))",
967
            args: "()",
968
            flags: NO_UNKNOWN_OPS,
969
            result: None,
970
            cost: 1000,
971
            err: "InvalidOperatorArg: softfork takes exactly 4 argument(s)",
972
        },
973
        RunProgramTest {
974
            prg: "(softfork (q . 939) (q . 9) (q x))",
975
            args: "()",
976
            flags: 0,
977
            result: Some("()"),
978
            cost: 1000,
979
            err: "",
980
        },
981
        RunProgramTest {
982
            prg: "(softfork (q . 939) (q . 9) (q x))",
983
            args: "()",
984
            flags: NO_UNKNOWN_OPS,
985
            result: None,
986
            cost: 1000,
987
            err: "InvalidOperatorArg: softfork takes exactly 4 argument(s)",
988
        },
989
        // this is a valid invocation, but we don't implement any extensions (yet)
990
        // so the extension specifier 0 is still unknown
991
        RunProgramTest {
992
            prg: "(softfork (q . 919) (q . 9) (q x) (q . ()))",
993
            args: "()",
994
            flags: 0,
995
            result: Some("()"),
996
            cost: 1000,
997
            err: "",
998
        },
999
        // when parsing the cost argument, we ignore redundant leading zeroes
1000
        RunProgramTest {
1001
            prg: "(softfork (q . 0x00000397) (q . 9) (q x) (q . ()))",
1002
            args: "()",
1003
            flags: 0,
1004
            result: Some("()"),
1005
            cost: 1000,
1006
            err: "",
1007
        },
1008
        RunProgramTest {
1009
            prg: "(softfork (q . 919) (q . 9) (q x) (q . ()))",
1010
            args: "()",
1011
            flags: NO_UNKNOWN_OPS,
1012
            result: None,
1013
            cost: 1000,
1014
            err: "unknown softfork extension",
1015
        },
1016

1017
        // this is a valid invocation, but we don't implement any extensions (yet)
1018
        RunProgramTest {
1019
            prg: "(softfork (q . 919) (q . 0x00ffffffff) (q x) (q . ()))",
1020
            args: "()",
1021
            flags: 0,
1022
            result: Some("()"),
1023
            cost: 1000,
1024
            err: "",
1025
        },
1026
        RunProgramTest {
1027
            prg: "(softfork (q . 919) (q . 0x00ffffffff) (q x) (q . ()))",
1028
            args: "()",
1029
            flags: NO_UNKNOWN_OPS,
1030
            result: None,
1031
            cost: 1000,
1032
            err: "unknown softfork extension",
1033
        },
1034

1035
        // we don't allow negative "extension" parameters
1036
        RunProgramTest {
1037
            prg: "(softfork (q . 919) (q . -1) (q x) (q . ()))",
1038
            args: "()",
1039
            flags: 0,
1040
            result: Some("()"),
1041
            cost: 1000,
1042
            err: "",
1043
        },
1044
        RunProgramTest {
1045
            prg: "(softfork (q . 919) (q . -1) (q x) (q . ()))",
1046
            args: "()",
1047
            flags: NO_UNKNOWN_OPS,
1048
            result: None,
1049
            cost: 1000,
1050
            err: "InvalidOperatorArg: softfork requires positive int arg",
1051
        },
1052

1053
        // we don't allow "extension" parameters > u32::MAX
1054
        RunProgramTest {
1055
            prg: "(softfork (q . 919) (q . 0x0100000000) (q x) (q . ()))",
1056
            args: "()",
1057
            flags: 0,
1058
            result: Some("()"),
1059
            cost: 1000,
1060
            err: "",
1061
        },
1062
        RunProgramTest {
1063
            prg: "(softfork (q . 919) (q . 0x0100000000) (q x) (q . ()))",
1064
            args: "()",
1065
            flags: NO_UNKNOWN_OPS,
1066
            result: None,
1067
            cost: 1000,
1068
            err: "InvalidOperatorArg: softfork requires u32 arg (with no leading zeros)",
1069
        },
1070

1071
        // we don't allow pairs as extension specifier
1072
        RunProgramTest {
1073
            prg: "(softfork (q . 919) (q 1 2 3) (q x) (q . ()))",
1074
            args: "()",
1075
            flags: 0,
1076
            result: Some("()"),
1077
            cost: 1000,
1078
            err: "",
1079
        },
1080
        RunProgramTest {
1081
            prg: "(softfork (q . 919) (q 1 2 3) (q x) (q . ()))",
1082
            args: "()",
1083
            flags: NO_UNKNOWN_OPS,
1084
            result: None,
1085
            cost: 1000,
1086
            err: "InvalidOperatorArg: Requires Int Argument: softfork",
1087
        },
1088

1089
        // the cost value is checked in consensus mode as well
1090
        RunProgramTest {
1091
            prg: "(softfork (q . 1000))",
1092
            args: "()",
1093
            flags: 0,
1094
            result: None,
1095
            cost: 1000,
1096
            err: "cost exceeded or below zero",
1097
        },
1098
        // the cost parameter is mandatory
1099
        RunProgramTest {
1100
            prg: "(softfork)",
1101
            args: "()",
1102
            flags: 0,
1103
            result: None,
1104
            cost: 0,
1105
            err: "InvalidOperatorArg: first of non-cons",
1106
        },
1107
        RunProgramTest {
1108
            prg: "(softfork (q . 0))",
1109
            args: "()",
1110
            flags: 0,
1111
            result: None,
1112
            cost: 1000,
1113
            err: "cost exceeded or below zero",
1114
        },
1115
        // negative costs are not allowed
1116
        RunProgramTest {
1117
            prg: "(softfork (q . -1))",
1118
            args: "()",
1119
            flags: 0,
1120
            result: None,
1121
            cost: 1000,
1122
            err: "InvalidOperatorArg: softfork requires positive int arg",
1123
        },
1124
        RunProgramTest {
1125
            prg: "(softfork (q 1 2 3))",
1126
            args: "()",
1127
            flags: 0,
1128
            result: None,
1129
            cost: 1000,
1130
            err: "InvalidOperatorArg: Requires Int Argument: softfork",
1131
        },
1132

1133
        // test mismatching cost
1134
        RunProgramTest {
1135
            prg: "(softfork (q . 160) (q . 0) (q . (q . 42)) (q . ()))",
1136
            args: "()",
1137
            flags: 0,
1138
            result: Some("()"),
1139
            cost: 241,
1140
            err: "",
1141
        },
1142
        // the program under the softfork is restricted by the specified cost
1143
        RunProgramTest {
1144
            prg: "(softfork (q . 159) (q . 0) (q . (q . 42)) (q . ()))",
1145
            args: "()",
1146
            flags: 0,
1147
            result: None,
1148
            cost: 241,
1149
            err: "cost exceeded or below zero",
1150
        },
1151
        // the cost specified on the softfork must match exactly the cost of
1152
        // executing the program
1153
        RunProgramTest {
1154
            prg: "(softfork (q . 161) (q . 0) (q . (q . 42)) (q . ()))",
1155
            args: "()",
1156
            flags: 0,
1157
            result: None,
1158
            cost: 10000,
1159
            err: "softfork specified cost mismatch",
1160
        },
1161

1162
        // without the flag to enable the keccak extensions, it's an unknown extension
1163
        RunProgramTest {
1164
            prg: "(softfork (q . 161) (q . 2) (q . (q . 42)) (q . ()))",
1165
            args: "()",
1166
            flags: NO_UNKNOWN_OPS,
1167
            result: None,
1168
            cost: 10000,
1169
            err: "unknown softfork extension",
1170
        },
1171

1172
        // coinid is also available under softfork extension 1
1173
        RunProgramTest {
1174
            prg: "(softfork (q . 1432) (q . 1) (q a (i (= (coinid (q . 0x1234500000000000000000000000000000000000000000000000000000000000) (q . 0x6789abcdef000000000000000000000000000000000000000000000000000000) (q . 123456789)) (q . 0x69bfe81b052bfc6bd7f3fb9167fec61793175b897c16a35827f947d5cc98e4bc)) (q . 0) (q x)) (q . ())) (q . ()))",
1175
            args: "()",
1176
            flags: 0,
1177
            result: Some("()"),
1178
            cost: 1513,
1179
            err: "",
1180
        },
1181

1182
        // keccak256 is available when the softfork has activated
1183
        RunProgramTest {
1184
            prg: "(softfork (q . 1134) (q . 1) (q a (i (= (keccak256 (q . \"foobar\")) (q . 0x38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e)) (q . 0) (q x)) (q . ())) (q . ()))",
1185
            args: "()",
1186
            flags: 0,
1187
            result: Some("()"),
1188
            cost: 1215,
1189
            err: "",
1190
        },
1191
        // make sure keccak is actually executed, by comparing with the wrong output
1192
        RunProgramTest {
1193
            prg: "(softfork (q . 1134) (q . 1) (q a (i (= (keccak256 (q . \"foobar\")) (q . 0x58d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e)) (q . 0) (q x)) (q . ())) (q . ()))",
1194
            args: "()",
1195
            flags: 0,
1196
            result: None,
1197
            cost: 1215,
1198
            err: "klvm raise",
1199
        },
1200

1201
        // === HARD FORK ===
1202
        // new operators *outside* the softfork guard
1203

1204
        // keccak256 is available outside the guard with the appropriate flag
1205
        RunProgramTest {
1206
            prg: "(a (i (= (keccak256 (q . \"foobar\")) (q . 0x38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e)) (q . 0) (q x)) (q . ()))",
1207
            args: "()",
1208
            flags: ENABLE_KECCAK_OPS_OUTSIDE_GUARD,
1209
            result: Some("()"),
1210
            cost: 994,
1211
            err: "",
1212
        },
1213

1214
        // coinid extension
1215
        // make sure we can execute the coinid operator under softfork 0
1216
        // this program raises an exception if the computed coin ID matches the
1217
        // expected
1218
        RunProgramTest {
1219
            prg: "(softfork (q . 1432) (q . 0) (q a (i (= (coinid (q . 0x1234500000000000000000000000000000000000000000000000000000000000) (q . 0x6789abcdef000000000000000000000000000000000000000000000000000000) (q . 123456789)) (q . 0x69bfe81b052bfc6bd7f3fb9167fec61793175b897c16a35827f947d5cc98e4bc)) (q x) (q . 0)) (q . ())) (q . ()))",
1220
            args: "()",
1221
            flags: 0,
1222
            result: None,
1223
            cost: 1513,
1224
            err: "klvm raise",
1225
        },
1226
        // also test the opposite. This program is the same as above but it raises
1227
        // if the coin ID is a mismatch
1228
        RunProgramTest {
1229
            prg: "(softfork (q . 1432) (q . 0) (q a (i (= (coinid (q . 0x1234500000000000000000000000000000000000000000000000000000000000) (q . 0x6789abcdef000000000000000000000000000000000000000000000000000000) (q . 123456789)) (q . 0x69bfe81b052bfc6bd7f3fb9167fec61793175b897c16a35827f947d5cc98e4bc)) (q . 0) (q x)) (q . ())) (q . ()))",
1230
            args: "()",
1231
            flags: 0,
1232
            result: Some("()"),
1233
            cost: 1513,
1234
            err: "",
1235
        },
1236

1237
        // coinid operator after hardfork, where coinid is available outside the
1238
        // softfork guard.
1239
        RunProgramTest {
1240
            prg: "(coinid (q . 0x1234500000000000000000000000000000000000000000000000000000000000) (q . 0x6789abcdef000000000000000000000000000000000000000000000000000000) (q . 123456789))",
1241
            args: "()",
1242
            flags: 0,
1243
            result: Some("0x69bfe81b052bfc6bd7f3fb9167fec61793175b897c16a35827f947d5cc98e4bc"),
1244
            cost: 861,
1245
            err: "",
1246
        },
1247
        RunProgramTest {
1248
            prg: "(coinid (q . 0x1234500000000000000000000000000000000000000000000000000000000000) (q . 0x6789abcdef000000000000000000000000000000000000000000000000000000) (q . 0x000123456789))",
1249
            args: "()",
1250
            flags: 0,
1251
            result: None,
1252
            cost: 861,
1253
            err: "InvalidOperatorArg: CoinID Error: Invalid Amount: Amount has leading zeroes",
1254
        },
1255

1256
        // secp261k1
1257

1258
        RunProgramTest {
1259
            prg: "(secp256k1_verify (q . 0x02888b0c110ef0b4962e3fc6929cbba7a8bb25b4b2c885f55c76365018c909b439) (q . 0x74c2941eb2ebe5aa4f2287a4c5e506a6290c045004058de97a7edf0122548668) (q . 0x1acb7a6e062e78ccd4237b12c22f02b5a8d9b33cb3ba13c35e88e036baa1cbca75253bb9a96ffc48b43196c69c2972d8f965b1baa4e52348d8081cde65e6c018))",
1260
            args: "()",
1261
            flags: 0,
1262
            result: Some("0"),
1263
            cost: 1300061,
1264
            err: "",
1265
        },
1266
        // invalid signature
1267
        RunProgramTest {
1268
            prg: "(secp256k1_verify (q . 0x02888b0c110ef0b4962e3fc6929cbba7a8bb25b4b2c885f55c76365018c909b439) (q . 0x74c2941eb2ebe5aa4f2287a4c5e506a6290c045004058de97a7edf0122548668) (q . 0x1acb7a6e062e78ccd4237b12c22f02b5a8d9b33cb3ba13c35e88e036baa1cbca75253bb9a96ffc48b43196c69c2972d8f965b1baa4e52348d8081cde65e6c019))",
1269
            args: "()",
1270
            flags: 0,
1271
            result: None,
1272
            cost: 0,
1273
            err: "Secp256 Verify Error: failed",
1274
        },
1275

1276
        // secp261r1
1277

1278
        RunProgramTest {
1279
            prg: "(secp256r1_verify (q . 0x0437a1674f3883b7171a11a20140eee014947b433723cf9f181a18fee4fcf96056103b3ff2318f00cca605e6f361d18ff0d2d6b817b1fa587e414f8bb1ab60d2b9) (q . 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08) (q . 0xe8de121f4cceca12d97527cc957cca64a4bcfc685cffdee051b38ee81cb22d7e2c187fec82c731018ed2d56f08a4a5cbc40c5bfe9ae18c02295bb65e7f605ffc))",
1280
            args: "()",
1281
            flags: 0,
1282
            result: Some("0"),
1283
            cost: 1850061,
1284
            err: "",
1285
        },
1286
        // invalid signature
1287
        RunProgramTest {
1288
            prg: "(secp256r1_verify (q . 0x0437a1674f3883b7171a11a20140eee014947b433723cf9f181a18fee4fcf96056103b3ff2318f00cca605e6f361d18ff0d2d6b817b1fa587e414f8bb1ab60d2b9) (q . 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08) (q . 0xe8de121f4cceca12d97527cc957cca64a4bcfc685cffdee051b38ee81cb22d7e2c187fec82c731018ed2d56f08a4a5cbc40c5bfe9ae18c02295bb65e7f605ffd))",
1289
            args: "()",
1290
            flags: 0,
1291
            result: None,
1292
            cost: 0,
1293
            err: "Secp256 Verify Error: failed",
1294
        },
1295
    ];
1296

1297
    fn check(res: (NodePtr, &str)) -> NodePtr {
830✔
1298
        assert_eq!(res.1, "");
830✔
1299
        res.0
830✔
1300
    }
830✔
1301

1302
    fn run_test_case(t: &RunProgramTest) {
335✔
1303
        use crate::chik_dialect::ChikDialect;
1304
        use crate::test_ops::node_eq;
1305
        let mut allocator = Allocator::new();
335✔
1306

1307
        let program = check(parse_exp(&mut allocator, t.prg));
335✔
1308
        let args = check(parse_exp(&mut allocator, t.args));
335✔
1309
        let expected_result = &t.result.map(|v| check(parse_exp(&mut allocator, v)));
335✔
1310

1311
        let dialect = ChikDialect::new(t.flags);
335✔
1312
        println!("prg: {}", t.prg);
335✔
1313
        match run_program(&mut allocator, &dialect, program, args, t.cost) {
335✔
1314
            Ok(Reduction(cost, prg_result)) => {
158✔
1315
                assert!(node_eq(&allocator, prg_result, expected_result.unwrap()));
158✔
1316
                assert_eq!(cost, t.cost);
158✔
1317

1318
                // now, run the same program again but with the cost limit 1 too low, to
1319
                // ensure it fails with the correct error
1320
                let expected_cost_exceeded =
158✔
1321
                    run_program(&mut allocator, &dialect, program, args, t.cost - 1).unwrap_err();
158✔
1322
                assert_eq!(expected_cost_exceeded, EvalErr::CostExceeded);
158✔
1323
            }
1324
            Err(err) => {
177✔
1325
                println!("FAILED: {err}");
177✔
1326
                assert_eq!(err.to_string(), t.err);
177✔
1327
                assert!(expected_result.is_none());
177✔
1328
            }
1329
        }
1330
    }
335✔
1331

1332
    #[test]
1333
    fn test_run_program() {
1✔
1334
        for t in TEST_CASES {
84✔
1335
            run_test_case(t);
83✔
1336
        }
83✔
1337
    }
1✔
1338

1339
    // the test cases for this test consists of:
1340
    // prg: the program to run inside the softfork guard
1341
    // cost: the expected cost of the program (the test adds the apply-operator)
1342
    // enabled: the softfork extension number that enables operator in prg
1343
    // hard_fork_flag: the flag that enables the program to be run outside the guard
1344
    // err: the expected error message, empty string means OK
1345
    // The test programs are carefully crafted such that they fail with "klvm raise"
1346
    // when run in consensus mode and the operators are unknown. e.g. (coinid ...)
1347
    // returns NIL in that case, which compares not equal to the coin ID, which
1348
    // raises the exception.
1349
    // This property is relied on for the non-mempool and fork-not-activated cases.
1350
    #[rstest]
1351
    // make sure we can execute the coinid operator under softfork 0
1352
    // this program raises an exception if the computed coin ID matches the
1353
    // expected
1354
    #[case::coinid(
1355
        "(i (= (coinid (q . 0x1234500000000000000000000000000000000000000000000000000000000000) (q . 0x6789abcdef000000000000000000000000000000000000000000000000000000) (q . 123456789)) (q . 0x69bfe81b052bfc6bd7f3fb9167fec61793175b897c16a35827f947d5cc98e4bd)) (q . 0) (q x))",
1356
        (1432, 0, 0),
1357
        "klvm raise")
1358
    ]
1359
    // also test the opposite. This program is the same as above but it raises
1360
    // if the coin ID is a mismatch
1361
    #[case::coinid(
1362
        "(i (= (coinid (q . 0x1234500000000000000000000000000000000000000000000000000000000000) (q . 0x6789abcdef000000000000000000000000000000000000000000000000000000) (q . 123456789)) (q . 0x69bfe81b052bfc6bd7f3fb9167fec61793175b897c16a35827f947d5cc98e4bc)) (q . 0) (q x))",
1363
        (1432, 0, 0),
1364
        ""
1365
    )]
1366
    // modpow
1367
    #[case::modpow(
1368
        "(i (= (modpow (q . 12345) (q . 6789) (q . 44444444444)) (q . 13456191581)) (q . 0) (q x))",
1369
        (18241, 0, 0),
1370
        ""
1371
    )]
1372
    #[case::modpow(
1373
        "(i (= (modpow (q . 12345) (q . 6789) (q . 44444444444)) (q . 13456191582)) (q . 0) (q x))",
1374
        (18241, 0, 0),
1375
        "klvm raise"
1376
    )]
1377
    // mod
1378
    #[case::modulus(
1379
        "(i (= (% (q . 80001) (q . 73)) (q . 66)) (q . 0) (q x))",
1380
        (1564, 0, 0),
1381
        ""
1382
    )]
1383
    #[case::modulus(
1384
        "(i (= (% (q . 80001) (q . 73)) (q . 67)) (q . 0) (q x))",
1385
        (1564, 0, 0),
1386
        "klvm raise"
1387
    )]
1388
    // g1_multiply
1389
    #[case::g1_mul(
1390
        "(i (= (g1_multiply  (q . 0x97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb) (q . 2)) (q . 0xa572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e)) (q . 0) (q x))",
1391
        (706634, 0, 0),
1392
        ""
1393
    )]
1394
    #[case::g1_mul(
1395
        "(i (= (g1_multiply  (q . 0x97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb) (q . 2)) (q . 0xa572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4f)) (q . 0) (q x))",
1396
        (706634, 0, 0),
1397
        "klvm raise"
1398
    )]
1399
    #[case::g1_neg(
1400
        "(i (= (g1_negate (q . 0xb7f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb)) (q . 0xb7f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb)) (q . 0) (q x))",
1401
        (706634, 0, 0),
1402
        "klvm raise"
1403
    )]
1404
    #[case::g1_neg(
1405
        "(i (= (g1_negate (q . 0xb2f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb)) (q . 0xb7f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb)) (q . 0) (q x))",
1406
        (706634, 0, 0),
1407
        "InvalidOperatorArg: atom is not a G1 point"
1408
    )]
1409
    #[case::g2_add(
1410
        "(i (= (g2_add (q . 0x93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8) (q . 0x93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8)) (q . 0xaa4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c335771638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053)) (q . 0) (q x))",
1411
        (3981700, 0, 0),
1412
        ""
1413
    )]
1414
    #[case::g2_add(
1415
        "(i (= (g2_add (q . 0x93e12b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8) (q . 0x93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8)) (q . 0xaa4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c335771638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053)) (q . 0) (q x))",
1416
        (3981700, 0, 0),
1417
        "InvalidAllocatorArg: atom is not a G2 point"
1418
    )]
1419
    #[case::keccak(
1420
        "(i (= (keccak256 (q . \"foobar\")) (q . 0x38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e)) (q . 0) (q x))",
1421
        (1134, 1, ENABLE_KECCAK_OPS_OUTSIDE_GUARD),
1422
        ""
1423
    )]
1424
    #[case::keccak(
1425
        "(i (= (keccak256 (q . \"foobar\")) (q . 0x38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873f)) (q . 0) (q x))",
1426
        (1134, 1, ENABLE_KECCAK_OPS_OUTSIDE_GUARD),
1427
        "klvm raise"
1428
    )]
1429
    fn test_softfork(
1430
        #[case] prg: &'static str,
1431
        #[case] fields: (u64, u8, u32), // cost, enabled, hard_fork_flag
1432
        #[case] err: &'static str,
1433
        #[values(0)] flags: u32,
1434
        #[values(false, true)] mempool: bool,
1435
        #[values(0, 1, 2)] test_ext: u8,
1436
    ) {
1437
        let (cost, enabled, hard_fork_flag) = fields;
1438
        let softfork_prg =
1439
            format!("(softfork (q . {cost}) (q . {test_ext}) (q . (a {prg} (q . 0))) (q . 0))");
1440

1441
        let flags = flags | if mempool { NO_UNKNOWN_OPS } else { 0 };
1442

1443
        // softfork extensions that are enabled
1444
        #[allow(clippy::match_like_matches_macro)]
1445
        let ext_enabled = match test_ext {
1446
            0 => true, // BLS
1447
            1 => true, // KECCAK
1448
            _ => false,
1449
        };
1450

1451
        println!("mempool: {mempool} ext: {test_ext} flags: {flags}");
1452
        let expect_err = match (ext_enabled as u8, (test_ext >= enabled) as u8) {
1453
            // the extension we're running has not been activated, and we're not
1454
            // running an extension that supports the operator
1455
            (0, 0) => {
1456
                if mempool {
1457
                    "unimplemented operator"
1458
                } else {
1459
                    ""
1460
                }
1461
            }
1462
            // the softfork extension hasn't been activated yet. It's a failure in
1463
            // mempool mode but ignored in consensus mode
1464
            (0, 1) => {
1465
                if mempool {
1466
                    "unknown softfork extension"
1467
                } else {
1468
                    ""
1469
                }
1470
            }
1471
            // the extension we're invoking has been enabled, but the operator is
1472
            // not part of this extension. In mempool mode it's an error, in
1473
            // consensus mode the operator is considered unknown, returning
1474
            // NIL/false. This in turn will make the return value test fail, and
1475
            // raise an exception.
1476
            (1, 0) => {
1477
                if mempool {
1478
                    "unimplemented operator"
1479
                } else {
1480
                    "klvm raise"
1481
                }
1482
            }
1483
            // the extension we're running has been activated, and we're running an
1484
            // extension the operator is available in. The program is executed and
1485
            // we get the expected result.
1486
            (1, 1) => err,
1487
            _ => unreachable!(),
1488
        };
1489

1490
        println!("expect: {expect_err} cost: {cost}");
1491
        let t = RunProgramTest {
1492
            prg: softfork_prg.as_str(),
1493
            args: "()",
1494
            flags,
1495
            result: if expect_err.is_empty() {
1496
                Some("()")
1497
            } else {
1498
                None
1499
            },
1500
            cost: cost + 81,
1501
            err: expect_err,
1502
        };
1503

1504
        run_test_case(&t);
1505

1506
        // now test outside the guard (should fail unless hard_fork_flag is set).
1507

1508
        let outside_guard_prg = format!("(a {prg} (q . 0))");
1509

1510
        // without the hard fork flag
1511
        println!("outside guard, no hard fork");
1512
        let t = RunProgramTest {
1513
            prg: outside_guard_prg.as_str(),
1514
            args: "()",
1515
            flags,
1516
            result: if err.is_empty() && hard_fork_flag == 0 {
1517
                Some("()")
1518
            } else {
1519
                None
1520
            },
1521
            cost: cost - 140,
1522
            err: if hard_fork_flag == 0 {
1523
                err
1524
            } else if mempool {
1525
                "unimplemented operator"
1526
            } else {
1527
                "klvm raise"
1528
            },
1529
        };
1530
        run_test_case(&t);
1531

1532
        // with the hard fork flag
1533
        println!("outside guard, hard fork activated");
1534
        let t = RunProgramTest {
1535
            prg: outside_guard_prg.as_str(),
1536
            args: "()",
1537
            flags: flags | hard_fork_flag,
1538
            result: if err.is_empty() { Some("()") } else { None },
1539
            cost: cost - 140,
1540
            err,
1541
        };
1542
        run_test_case(&t);
1543
    }
1544

1545
    #[cfg(feature = "counters")]
1546
    #[test]
1547
    fn test_counters() {
1✔
1548
        use crate::chik_dialect::ChikDialect;
1549

1550
        let mut a = Allocator::new();
1✔
1551

1552
        let program = check(parse_exp(&mut a, "(a (q 2 2 (c 2 (c 5 (c 11 ())))) (c (q 2 (i (= 11 ()) (q 1 . 1) (q 18 5 (a 2 (c 2 (c 5 (c (- 11 (q . 1)) ())))))) 1) 1))"));
1✔
1553
        let args = check(parse_exp(&mut a, "(5033 1000)"));
1✔
1554
        let cost = 15073165;
1✔
1555

1556
        let (counters, result) =
1✔
1557
            run_program_with_counters(&mut a, &ChikDialect::new(0), program, args, cost);
1✔
1558

1559
        assert_eq!(counters.val_stack_usage, 3015);
1✔
1560
        assert_eq!(counters.env_stack_usage, 1005);
1✔
1561
        assert_eq!(counters.op_stack_usage, 3014);
1✔
1562
        assert_eq!(counters.atom_count, 998);
1✔
1563
        assert_eq!(counters.small_atom_count, 1042);
1✔
1564
        assert_eq!(counters.pair_count, 22077);
1✔
1565
        assert_eq!(counters.heap_size, 769963);
1✔
1566

1567
        assert_eq!(result.unwrap().0, cost);
1✔
1568
    }
1✔
1569
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc