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

Chik-Network / klvm_rs / 9969783315

17 Jul 2024 07:09AM UTC coverage: 91.834% (-2.2%) from 94.072%
9969783315

push

github

Chik-Network
update 0.2.5

1191 of 1284 new or added lines in 29 files covered. (92.76%)

66 existing lines in 11 files now uncovered.

4071 of 4433 relevant lines covered (91.83%)

3553.81 hits per line

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

98.33
/src/run_program.rs
1
use super::traverse_path::traverse_path;
2
use crate::allocator::{Allocator, AtomBuf, Checkpoint, NodePtr, SExp};
3
use crate::cost::Cost;
4
use crate::dialect::{Dialect, OperatorSet};
5
use crate::err_utils::err;
6
use crate::node::Node;
7
use crate::op_utils::uint_atom;
8
use crate::reduction::{EvalErr, Reduction, Response};
9

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

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

23
#[cfg(feature = "pre-eval")]
24
pub type PostEval = dyn Fn(Option<NodePtr>);
25

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

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

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

48
#[cfg(feature = "counters")]
49
impl Counters {
50
    fn new() -> Self {
51
        Counters {
52
            val_stack_usage: 0,
53
            env_stack_usage: 0,
54
            op_stack_usage: 0,
55
            atom_count: 0,
56
            pair_count: 0,
57
            heap_size: 0,
58
        }
59
    }
60
}
61

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

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

75
    // this specifies which new operators are available
76
    operator_set: OperatorSet,
77

78
    #[cfg(test)]
79
    start_cost: Cost,
80
}
81

82
// `run_program` has two stacks: the operand stack (of `Node` objects) and the
83
// operator stack (of Operation)
84

85
struct RunProgramContext<'a, D> {
86
    allocator: &'a mut Allocator,
87
    dialect: &'a D,
88
    val_stack: Vec<NodePtr>,
89
    env_stack: Vec<NodePtr>,
90
    op_stack: Vec<Operation>,
91
    softfork_stack: Vec<SoftforkGuard>,
92
    stack_limit: usize,
93
    #[cfg(feature = "counters")]
94
    pub counters: Counters,
95

96
    #[cfg(feature = "pre-eval")]
97
    pre_eval: Option<PreEval>,
98
    #[cfg(feature = "pre-eval")]
99
    posteval_stack: Vec<Box<PostEval>>,
100
}
101

102
fn augment_cost_errors(r: Result<Cost, EvalErr>, max_cost: NodePtr) -> Result<Cost, EvalErr> {
56,549✔
103
    r.map_err(|e| {
56,549✔
104
        if &e.1 != "cost exceeded" {
33✔
105
            e
21✔
106
        } else {
107
            EvalErr(max_cost, e.1)
12✔
108
        }
109
    })
56,549✔
110
}
56,549✔
111

112
impl<'a, D: Dialect> RunProgramContext<'a, D> {
113
    #[cfg(feature = "counters")]
114
    #[inline(always)]
115
    fn account_val_push(&mut self) {
116
        self.counters.val_stack_usage =
117
            std::cmp::max(self.counters.val_stack_usage, self.val_stack.len());
118
    }
119

120
    #[cfg(feature = "counters")]
121
    #[inline(always)]
122
    fn account_env_push(&mut self) {
123
        self.counters.env_stack_usage =
124
            std::cmp::max(self.counters.env_stack_usage, self.env_stack.len());
125
    }
126

127
    #[cfg(feature = "counters")]
128
    #[inline(always)]
129
    fn account_op_push(&mut self) {
130
        self.counters.op_stack_usage =
131
            std::cmp::max(self.counters.op_stack_usage, self.op_stack.len());
132
    }
133

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

138
    #[cfg(not(feature = "counters"))]
139
    #[inline(always)]
140
    fn account_env_push(&mut self) {}
18,183✔
141

142
    #[cfg(not(feature = "counters"))]
143
    #[inline(always)]
144
    fn account_op_push(&mut self) {}
94,918✔
145

146
    pub fn pop(&mut self) -> Result<NodePtr, EvalErr> {
189,879✔
147
        let v: Option<NodePtr> = self.val_stack.pop();
189,879✔
148
        match v {
189,879✔
149
            None => {
NEW
150
                let node: NodePtr = self.allocator.null();
×
151
                err(node, "runtime error: value stack empty")
×
152
            }
153
            Some(k) => Ok(k),
189,879✔
154
        }
155
    }
189,879✔
156
    pub fn push(&mut self, node: NodePtr) -> Result<(), EvalErr> {
189,920✔
157
        if self.val_stack.len() == self.stack_limit {
189,920✔
158
            return err(node, "value stack limit reached");
×
159
        }
189,920✔
160
        self.val_stack.push(node);
189,920✔
161
        self.account_val_push();
189,920✔
162
        Ok(())
189,920✔
163
    }
189,920✔
164

165
    pub fn push_env(&mut self, env: NodePtr) -> Result<(), EvalErr> {
18,183✔
166
        if self.env_stack.len() == self.stack_limit {
18,183✔
167
            return err(env, "environment stack limit reached");
×
168
        }
18,183✔
169
        self.env_stack.push(env);
18,183✔
170
        self.account_env_push();
18,183✔
171
        Ok(())
18,183✔
172
    }
18,183✔
173

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

195
    fn new(allocator: &'a mut Allocator, dialect: &'a D) -> Self {
119✔
196
        RunProgramContext {
119✔
197
            allocator,
119✔
198
            dialect,
119✔
199
            val_stack: Vec::new(),
119✔
200
            env_stack: Vec::new(),
119✔
201
            op_stack: Vec::new(),
119✔
202
            softfork_stack: Vec::new(),
119✔
203
            stack_limit: dialect.stack_limit(),
119✔
204
            #[cfg(feature = "counters")]
119✔
205
            counters: Counters::new(),
119✔
206
            #[cfg(feature = "pre-eval")]
119✔
207
            pre_eval: None,
119✔
208
            #[cfg(feature = "pre-eval")]
119✔
209
            posteval_stack: Vec::new(),
119✔
210
        }
119✔
211
    }
119✔
212

213
    fn cons_op(&mut self) -> Result<Cost, EvalErr> {
38,366✔
214
        /* Join the top two operands. */
215
        let v1 = self.pop()?;
38,366✔
216
        let v2 = self.pop()?;
38,366✔
217
        let p = self.allocator.new_pair(v1, v2)?;
38,366✔
218
        self.push(p)?;
38,366✔
219
        Ok(0)
38,366✔
220
    }
38,366✔
221

222
    fn eval_op_atom(
24,412✔
223
        &mut self,
24,412✔
224
        op_buf: &AtomBuf,
24,412✔
225
        operator_node: NodePtr,
24,412✔
226
        operand_list: NodePtr,
24,412✔
227
        env: NodePtr,
24,412✔
228
    ) -> Result<Cost, EvalErr> {
24,412✔
229
        let op_atom = self.allocator.buf(op_buf);
24,412✔
230
        // special case check for quote
24,412✔
231
        if op_atom == self.dialect.quote_kw() {
24,412✔
232
            self.push(operand_list)?;
6,231✔
233
            Ok(QUOTE_COST)
6,231✔
234
        } else {
235
            self.push_env(env)?;
18,181✔
236
            self.op_stack.push(Operation::Apply);
18,181✔
237
            self.account_op_push();
18,181✔
238
            self.push(operator_node)?;
18,181✔
239
            let mut operands: NodePtr = operand_list;
18,181✔
240
            while let SExp::Pair(first, rest) = self.allocator.sexp(operands) {
56,549✔
241
                // We evaluate every entry in the argument list (using the
242
                // environment at the top of the env_stack) The resulting return
243
                // values are arranged in a list. the top item on the stack is
244
                // the resulting list, and below it is the next pair to
245
                // evaluated.
246
                //
247
                // each evaluation pops both, pushes the result list
248
                // back, evaluates and the executes the Cons operation
249
                // to add the most recent result to the list. Leaving
250
                // the new list at the top of the stack for the next
251
                // pair to be evaluated.
252
                self.op_stack.push(Operation::SwapEval);
38,368✔
253
                self.account_op_push();
38,368✔
254
                self.push(first)?;
38,368✔
255
                operands = rest;
38,368✔
256
            }
257
            // ensure a correct null terminator
258
            if !self.allocator.atom(operands).is_empty() {
18,181✔
UNCOV
259
                err(operand_list, "bad operand list")
×
260
            } else {
261
                self.push(self.allocator.null())?;
18,181✔
262
                Ok(OP_COST)
18,181✔
263
            }
264
        }
265
    }
24,412✔
266

267
    fn eval_pair(&mut self, program: NodePtr, env: NodePtr) -> Result<Cost, EvalErr> {
42,507✔
268
        #[cfg(feature = "pre-eval")]
269
        if let Some(pre_eval) = &self.pre_eval {
270
            if let Some(post_eval) = pre_eval(self.allocator, program, env)? {
271
                self.posteval_stack.push(post_eval);
272
                self.op_stack.push(Operation::PostEval);
273
            }
274
        };
275

276
        // put a bunch of ops on op_stack
277
        let (op_node, op_list) = match self.allocator.sexp(program) {
42,507✔
278
            // the program is just a bitfield path through the env tree
279
            SExp::Atom(path) => {
18,092✔
280
                let r: Reduction = traverse_path(self.allocator, self.allocator.buf(&path), env)?;
18,092✔
281
                self.push(r.1)?;
18,091✔
282
                return Ok(r.0);
18,091✔
283
            }
284
            // the program is an operator and a list of operands
285
            SExp::Pair(operator_node, operand_list) => (operator_node, operand_list),
24,415✔
286
        };
24,415✔
287

24,415✔
288
        match self.allocator.sexp(op_node) {
24,415✔
289
            SExp::Pair(new_operator, _) => {
3✔
290
                let op_node = Node::new(self.allocator, op_node);
3✔
291
                if !op_node.arg_count_is(1) || op_node.first()?.atom().is_none() {
3✔
292
                    return err(program, "in ((X)...) syntax X must be lone atom");
1✔
293
                }
2✔
294
                self.push_env(env)?;
2✔
295
                self.push(new_operator)?;
2✔
296
                self.push(op_list)?;
2✔
297
                self.op_stack.push(Operation::Apply);
2✔
298
                self.account_op_push();
2✔
299
                Ok(APPLY_COST)
2✔
300
            }
301
            SExp::Atom(op_atom) => self.eval_op_atom(&op_atom, op_node, op_list, env),
24,412✔
302
        }
303
    }
42,507✔
304

305
    fn swap_eval_op(&mut self) -> Result<Cost, EvalErr> {
38,367✔
306
        let v2 = self.pop()?;
38,367✔
307
        let program: NodePtr = self.pop()?;
38,367✔
308
        let env: NodePtr = *self
38,367✔
309
            .env_stack
38,367✔
310
            .last()
38,367✔
311
            .ok_or_else(|| EvalErr(program, "runtime error: env stack empty".into()))?;
38,367✔
312
        self.push(v2)?;
38,367✔
313

314
        // on the way back, build a list from the values
315
        self.op_stack.push(Operation::Cons);
38,367✔
316
        self.account_op_push();
38,367✔
317

38,367✔
318
        self.eval_pair(program, env)
38,367✔
319
    }
38,367✔
320

321
    fn parse_softfork_arguments(
23✔
322
        &self,
23✔
323
        args: &Node,
23✔
324
    ) -> Result<(OperatorSet, NodePtr, NodePtr), EvalErr> {
23✔
325
        if !args.arg_count_is(4) {
23✔
326
            return Err(EvalErr(
6✔
327
                args.node,
6✔
328
                "softfork takes exactly 4 arguments".to_string(),
6✔
329
            ));
6✔
330
        }
17✔
331
        let args = args.rest()?;
17✔
332

333
        let extension = self
17✔
334
            .dialect
17✔
335
            .softfork_extension(uint_atom::<4>(&args.first()?, "softfork")? as u32);
17✔
336
        if extension == OperatorSet::Default {
11✔
337
            return Err(EvalErr(args.node, "unknown softfork extension".to_string()));
6✔
338
        }
5✔
339
        let args = args.rest()?;
5✔
340
        let program = args.first()?.node;
5✔
341
        let args = args.rest()?;
5✔
342
        let env = args.first()?.node;
5✔
343

344
        Ok((extension, program, env))
5✔
345
    }
23✔
346

347
    fn apply_op(&mut self, current_cost: Cost, max_cost: Cost) -> Result<Cost, EvalErr> {
18,182✔
348
        let operand_list = self.pop()?;
18,182✔
349
        let operator = self.pop()?;
18,182✔
350
        let operand_list = Node::new(self.allocator, operand_list);
18,182✔
351
        let operator = Node::new(self.allocator, operator);
18,182✔
352
        let op_atom = operator
18,182✔
353
            .atom()
18,182✔
354
            .ok_or_else(|| EvalErr(operator.node, "internal error".into()))?;
18,182✔
355
        self.env_stack
18,182✔
356
            .pop()
18,182✔
357
            .ok_or_else(|| EvalErr(operator.node, "runtime error: env stack empty".into()))?;
18,182✔
358
        if op_atom == self.dialect.apply_kw() {
18,182✔
359
            if !operand_list.arg_count_is(2) {
4,018✔
360
                return operand_list.err("apply requires exactly 2 parameters");
2✔
361
            }
4,016✔
362

363
            let new_operator = operand_list.first()?.node;
4,016✔
364
            let env = operand_list.rest()?.first()?.node;
4,016✔
365

366
            self.eval_pair(new_operator, env).map(|c| c + APPLY_COST)
4,016✔
367
        } else if op_atom == self.dialect.softfork_kw() {
14,164✔
368
            let expected_cost = uint_atom::<8>(&operand_list.first()?, "softfork")?;
39✔
369
            if expected_cost > max_cost {
36✔
370
                return err(self.allocator.null(), "cost exceeded");
12✔
371
            }
24✔
372
            if expected_cost == 0 {
24✔
373
                return err(self.allocator.null(), "cost must be > 0");
1✔
374
            }
23✔
375

376
            // we can't blindly propagate errors here, since we handle errors
377
            // differently depending on whether we allow unknown ops or not
378
            let (ext, prg, env) = match self.parse_softfork_arguments(&operand_list) {
23✔
379
                Ok(ret_values) => ret_values,
5✔
380
                Err(err) => {
18✔
381
                    if self.dialect.allow_unknown_ops() {
18✔
382
                        // In this case, we encountered a softfork invocation
383
                        // that doesn't pass the correct arguments.
384
                        // if we're in consensus mode, we have to accept this as
385
                        // something we don't understand
386
                        self.push(self.allocator.null())?;
9✔
387
                        return Ok(expected_cost);
9✔
388
                    }
9✔
389
                    return Err(err);
9✔
390
                }
391
            };
392

393
            self.softfork_stack.push(SoftforkGuard {
5✔
394
                expected_cost: current_cost + expected_cost,
5✔
395
                allocator_state: self.allocator.checkpoint(),
5✔
396
                operator_set: ext,
5✔
397
                #[cfg(test)]
5✔
398
                start_cost: current_cost,
5✔
399
            });
5✔
400

5✔
401
            // once the softfork guard exits, we need to ensure the cost that was
5✔
402
            // specified match the true cost. We also free heap allocations
5✔
403
            self.op_stack.push(Operation::ExitGuard);
5✔
404

5✔
405
            self.eval_pair(prg, env).map(|c| c + GUARD_COST)
5✔
406
        } else {
407
            let current_extensions = if let Some(sf) = self.softfork_stack.last() {
14,125✔
408
                sf.operator_set
7✔
409
            } else {
410
                OperatorSet::Default
14,118✔
411
            };
412

413
            let r = self.dialect.op(
14,125✔
414
                self.allocator,
14,125✔
415
                operator.node,
14,125✔
416
                operand_list.node,
14,125✔
417
                max_cost,
14,125✔
418
                current_extensions,
14,125✔
419
            )?;
14,125✔
420
            self.push(r.1)?;
14,120✔
421
            Ok(r.0)
14,120✔
422
        }
423
    }
18,182✔
424

425
    fn exit_guard(&mut self, current_cost: Cost) -> Result<Cost, EvalErr> {
3✔
426
        // this is called when we are done executing a softfork program.
3✔
427
        // This is when we have to validate the cost
3✔
428
        let guard = self
3✔
429
            .softfork_stack
3✔
430
            .pop()
3✔
431
            .expect("internal error. exiting a softfork that's already been popped");
3✔
432

3✔
433
        if current_cost != guard.expected_cost {
3✔
434
            #[cfg(test)]
435
            println!(
1✔
436
                "actual cost: {} specified cost: {}",
1✔
437
                current_cost - guard.start_cost,
1✔
438
                guard.expected_cost - guard.start_cost
1✔
439
            );
1✔
440
            return err(self.allocator.null(), "softfork specified cost mismatch");
1✔
441
        }
2✔
442

2✔
443
        // restore the allocator to the state when we entered the softfork guard
2✔
444
        // This is an optimization to reclaim all heap space allocated by the
2✔
445
        // softfork program. Since the softfork always return null, no value can
2✔
446
        // escape the softfork program, and it's therefore safe to restore the
2✔
447
        // heap
2✔
448
        self.allocator.restore_checkpoint(&guard.allocator_state);
2✔
449

2✔
450
        // the softfork always returns null, pop the value pushed by the
2✔
451
        // evaluation of the program and push null instead
2✔
452
        self.pop()
2✔
453
            .expect("internal error, softfork program did not push value onto stack");
2✔
454

2✔
455
        self.push(self.allocator.null())?;
2✔
456

457
        Ok(0)
2✔
458
    }
3✔
459

460
    pub fn run_program(&mut self, program: NodePtr, env: NodePtr, max_cost: Cost) -> Response {
119✔
461
        self.val_stack = vec![];
119✔
462
        self.op_stack = vec![];
119✔
463

464
        // max_cost is always in effect, and necessary to prevent wrap-around of
465
        // the cost integer.
466
        let max_cost = if max_cost == 0 { Cost::MAX } else { max_cost };
119✔
467
        let max_cost_ptr = self.allocator.new_number(max_cost.into())?;
119✔
468

469
        let mut cost: Cost = 0;
119✔
470

119✔
471
        cost += self.eval_pair(program, env)?;
119✔
472

473
        loop {
474
            // if we are in a softfork guard, temporarily use the guard's
475
            // expected cost as the upper limit. This lets us fail early in case
476
            // it's wrong. It's guaranteed to be <= max_cost, because we check
477
            // that when entering the softfork guard
478
            let effective_max_cost = if let Some(sf) = self.softfork_stack.last() {
95,002✔
479
                sf.expected_cost
53✔
480
            } else {
481
                max_cost
94,949✔
482
            };
483

484
            if cost > effective_max_cost {
95,002✔
485
                return err(max_cost_ptr, "cost exceeded");
37✔
486
            }
94,965✔
487
            let top = self.op_stack.pop();
94,965✔
488
            let op = match top {
94,965✔
489
                Some(f) => f,
94,918✔
490
                None => break,
47✔
491
            };
492
            cost += match op {
94,918✔
493
                Operation::Apply => augment_cost_errors(
18,182✔
494
                    self.apply_op(cost, effective_max_cost - cost),
18,182✔
495
                    max_cost_ptr,
18,182✔
496
                )?,
18,182✔
497
                Operation::ExitGuard => self.exit_guard(cost)?,
3✔
498
                Operation::Cons => self.cons_op()?,
38,366✔
499
                Operation::SwapEval => augment_cost_errors(self.swap_eval_op(), max_cost_ptr)?,
38,367✔
500
                #[cfg(feature = "pre-eval")]
501
                Operation::PostEval => {
502
                    let f = self.posteval_stack.pop().unwrap();
503
                    let peek: Option<NodePtr> = self.val_stack.last().copied();
504
                    f(peek);
505
                    0
506
                }
507
            };
508
        }
509
        Ok(Reduction(cost, self.pop()?))
47✔
510
    }
119✔
511
}
512

513
pub fn run_program<'a, D: Dialect>(
119✔
514
    allocator: &'a mut Allocator,
119✔
515
    dialect: &'a D,
119✔
516
    program: NodePtr,
119✔
517
    env: NodePtr,
119✔
518
    max_cost: Cost,
119✔
519
) -> Response {
119✔
520
    let mut rpc = RunProgramContext::new(allocator, dialect);
119✔
521
    rpc.run_program(program, env, max_cost)
119✔
522
}
119✔
523

524
#[cfg(feature = "pre-eval")]
525
pub fn run_program_with_pre_eval<'a, D: Dialect>(
526
    allocator: &'a mut Allocator,
527
    dialect: &'a D,
528
    program: NodePtr,
529
    env: NodePtr,
530
    max_cost: Cost,
531
    pre_eval: Option<PreEval>,
532
) -> Response {
533
    let mut rpc = RunProgramContext::new_with_pre_eval(allocator, dialect, pre_eval);
534
    rpc.run_program(program, env, max_cost)
535
}
536

537
#[cfg(feature = "counters")]
538
pub fn run_program_with_counters<'a, D: Dialect>(
539
    allocator: &'a mut Allocator,
540
    dialect: &'a D,
541
    program: NodePtr,
542
    env: NodePtr,
543
    max_cost: Cost,
544
) -> (Counters, Response) {
545
    let mut rpc = RunProgramContext::new(allocator, dialect);
546
    let ret = rpc.run_program(program, env, max_cost);
547
    rpc.counters.atom_count = rpc.allocator.atom_count() as u32;
548
    rpc.counters.pair_count = rpc.allocator.pair_count() as u32;
549
    rpc.counters.heap_size = rpc.allocator.heap_size() as u32;
550
    (rpc.counters, ret)
551
}
552

553
#[cfg(test)]
554
struct RunProgramTest {
555
    prg: &'static str,
556
    args: &'static str,
557
    flags: u32,
558
    result: Option<&'static str>,
559
    cost: Cost,
560
    err: &'static str,
561
}
562

563
#[cfg(test)]
564
use crate::test_ops::parse_exp;
565

566
#[cfg(test)]
567
use crate::chik_dialect::ENABLE_BLS_OPS;
568
#[cfg(test)]
569
use crate::chik_dialect::ENABLE_BLS_OPS_OUTSIDE_GUARD;
570
#[cfg(test)]
571
use crate::chik_dialect::NO_UNKNOWN_OPS;
572

573
#[cfg(test)]
574
const TEST_CASES: &[RunProgramTest] = &[
575
    // (mod (X N) (defun power (X N) (if (= N 0) 1 (* X (power X (- N 1))))) (power X N))
576
    RunProgramTest {
577
        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))",
578
        args: "(5033 1000)",
579
        flags: 0,
580
        result: Some("0x024d4f505f1f813ca5e0ae8805bad8707347e65c5f7595da4852be5074288431d1df11a0c326d249f1f52ee051579403d1d0c23a7a1e9af18b7d7dc4c63c73542863c434ae9dfa80141a30cf4acee0d6c896aa2e64ea748404427a3bdaa1b97e4e09b8f5e4f8e9c568a4fc219532dbbad5ec54476d19b7408f8e7e7df16b830c20a1e83d90cc0620b0677b7606307f725539ef223561cdb276baf8e92156ee6492d97159c8f64768349ea7e219fd07fa818a59d81d0563b140396402f0ff758840da19808440e0a57c94c48ef84b4ab7ca8c5f010b69b8f443b12b50bd91bdcf2a96208ddac283fa294d6a99f369d57ab41d03eab5bb4809223c141ad94378516e6766a5054e22e997e260978af68a86893890d612f081b40d54fd1e940af35c0d7900c9a917e2458a61ef8a83f7211f519b2c5f015dfa7c2949ef8bedd02d3bad64ca9b2963dc2bb79f24092331133a7a299872079b9d0422b8fc0eeba4e12c7667ac7282cc6ff98a7c670614c9fce5a061b8d5cd4dd3c6d62d245688b62f9713dc2604bdd5bbc85c070c51f784a9ebac0e0eaa2e29e82d93e570887aa7e1a9d25baf0b2c55a4615f35ec0dbe9baa921569700f95e10cd2d4f6ba152a2ac288c37b60980df33dadfa920fd43dbbf55a0b333b88a3237d954e33d80ed6582019faf51db5f1b52e392559323f8bdd945e7fc6cb8f97f2b8417cfc184d7bfbfa5314d4114f95b725847523f1848d13c28ad96662298ee4e2d87af23e7cb4e58d7a20a5c57ae6833b4a37dcafccca0245a0d6ef28f83200d74db390281e03dd3a8b782970895764c3fcef31c5ed6d0b6e4e796a62ad5654691eea0d9db351cc4fee63248405b24c98bd5e68e4a5e0ab11e90e3c7de270c594d3a35639d931853b7010c8c896f6b28b2af719e53da65da89d44b926b6f06123c9217a43be35d751516bd02c18c4f868a2eae78ae3c6deab1115086c8ce58414db4561865d17ab95c7b3d4e1bfc6d0a4d3fbf5f20a0a7d77a9270e4da354c588da55b0063aec76654019ffb310e1503d99a7bc81ccdf5f8b15c8638156038624cf35988d8420bfdb59184c4b86bf5448df65c44aedc2e98eead7f1ba4be8f402baf12d41076b8f0991cfc778e04ba2c05d1440c70488ffaeefde537064035037f729b683e8ff1b3d0b4aa26a2b30bcaa9379f7fcc7072ff9a2c3e801c5979b0ab3e7acf89373de642d596f26514b9fa213ca217181a8429ad69d14445a822b16818c2509480576dc0ff7bac48c557e6d1883039f4daf873fa4f9a4d849130e2e4336049cfaf9e69a7664f0202b901cf07c7065c4dc93c46f98c5ea5c9c9d911b733093490da3bf1c95f43cd18b7be3798535a55ac6da3442946a268b74bde1349ca9807c41d90c7ec218a17efd2c21d5fcd720501f8a488f1dfba0a423dfdb2a877707b77930e80d734ceabcdb24513fad8f2e2470604d041df083bf184edd0e9720dd2b608b1ee1df951d7ce8ec671317b4f5a3946aa75280658b4ef77b3f504ce73e7ecac84eec3c2b45fb62f6fbd5ab78c744abd3bf5d0ab37d7b19124d2470d53db09ddc1f9dd9654b0e6a3a44c95d0a5a5e061bd24813508d3d1c901544dc3e6b84ca38dd2fde5ea60a57cbc12428848c4e3f6fd4941ebd23d709a717a090dd01830436659f7c20fd2d70c916427e9f3f12ac479128c2783f02a9824aa4e31de133c2704e049a50160f656e28aa0a2615b32bd48bb5d5d13d363a487324c1e9b8703be938bc545654465c9282ad5420978263b3e3ba1bb45e1a382554ac68e5a154b896c9c4c2c3853fbbfc877c4fb7dc164cc420f835c413839481b1d2913a68d206e711fb19b284a7bb2bd2033531647cf135833a0f3026b0c1dc0c184120d30ef4865985fdacdfb848ab963d2ae26a784b7b6a64fdb8feacf94febed72dcd0a41dc12be26ed79af88f1d9cba36ed1f95f2da8e6194800469091d2dfc7b04cfe93ab7a7a888b2695bca45a76a1458d08c3b6176ab89e7edc56c7e01142adfff944641b89cd5703a911145ac4ec42164d90b6fcd78b39602398edcd1f935485894fb8a1f416e031624806f02fbd07f398dbfdd48b86dfacf2045f85ecfe5bb1f01fae758dcdb4ae3b1e2aac6f0878f700d1f430b8ca47c9d8254059bd5c006042c4605f33ca98b41"),
581
        cost: 15073165,
582
        err: "",
583
    },
584
    // '
585
    RunProgramTest {
586
        prg: "(= (point_add (pubkey_for_exp (q . -2)) (pubkey_for_exp (q . 5))) (pubkey_for_exp (q . 3)))",
587
        args: "()",
588
        flags: 0,
589
        result: Some("1"),
590
        cost: 6768556,
591
        err: "",
592
    },
593
    RunProgramTest {
594
        prg: "(= (point_add (pubkey_for_exp (q . 2)) (pubkey_for_exp (q . 3))) (pubkey_for_exp (q . 5)))",
595
        args: "()",
596
        flags: 0,
597
        result: Some("1"),
598
        cost: 6768556,
599
        err: "",
600
    },
601
    RunProgramTest {
602
        prg: "(point_add (pubkey_for_exp (q . 1)) (pubkey_for_exp (q . 2)))",
603
        args: "()",
604
        flags: 0,
605
        result: Some("0x89ece308f9d1f0131765212deca99697b112d61f9be9a5f1f3780a51335b3ff981747a0b2ca2179b96d2c0c9024e5224"),
606
        cost: 5442073,
607
        err: "",
608
    },
609
    RunProgramTest {
610
        prg: "(f (f (q . ((100 200 300) 400 500))))",
611
        args: "()",
612
        flags: 0,
613
        result: Some("0x64"),
614
        cost: 82,
615
        err: "",
616
    },
617
    RunProgramTest {
618
        prg: "(= (f 1) (+ (f (r 1)) (f (r (r 1)))))",
619
        args: "(7 3 3)",
620
        flags: 0,
621
        result: Some("()"),
622
        cost: 1194,
623
        err: "",
624
    },
625
    RunProgramTest {
626
        prg: "(= (f 1) (+ (f (r 1)) (f (r (r 1)))))",
627
        args: "(7 3 4)",
628
        flags: 0,
629
        result: Some("1"),
630
        cost: 1194,
631
        err: "",
632
    },
633
    RunProgramTest {
634
        prg: "(i (f (r (r 1))) (f 1) (f (r 1)))",
635
        args: "(200 300 400)",
636
        flags: 0,
637
        result: Some("0x00c8"),
638
        cost: 352,
639
        err: "",
640
    },
641
    RunProgramTest {
642
        prg: "(i (f (r (r 1))) (f 1) (f (r 1)))",
643
        args: "(200 300 1)",
644
        flags: 0,
645
        result: Some("0x00c8"),
646
        cost: 352,
647
        err: "",
648
    },
649
    RunProgramTest {
650
        prg: "(r (r (q . ((100 200 300) 400 500))))",
651
        args: "()",
652
        flags: 0,
653
        result: Some("(500)"),
654
        cost: 82,
655
        err: "",
656
    },
657
    RunProgramTest {
658
        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))",
659
        args: "()",
660
        flags: 0,
661
        result: Some("0x04261a5c969abab851babdb4f178e63bf2ed3879fc13a4c75622d73c909440a4763849b52e49cd2522500f555f6a3131775f93ddcf24eda7a1dbdf828a033626da873caaaa880a9121f4c44a157973f60443dc53bc99ac12d5bd5fa20a88320ae2ccb8e1b5e792cbf0d001bb0fbd7765d3936e412e2fc8f1267833237237fcb638dda0a7aa674680000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
662
        cost: 24255,
663
        err: "",
664
    },
665

666
    // ## APPLY
667
    RunProgramTest {
668
        prg: "(a (q 0x0fffffffff) (q ()))",
669
        args: "()",
670
        flags: 0,
671
        result: None,
672
        cost: 0,
673
        err: "invalid operator",
674
    },
675
    RunProgramTest {
676
        prg: "(a (q . 0) (q . 1) (q . 2))",
677
        args: "()",
678
        flags: 0,
679
        result: None,
680
        cost: 0,
681
        err: "apply requires exactly 2 parameters",
682
    },
683
    RunProgramTest {
684
        prg: "(a (q 0x00ffffffffffffffffffff00) (q ()))",
685
        args: "()",
686
        flags: 0,
687
        result: None,
688
        cost: 0,
689
        err: "invalid operator",
690
    },
691
    RunProgramTest {
692
        prg: "(a (q . 1))",
693
        args: "()",
694
        flags: 0,
695
        result: None,
696
        cost: 0,
697
        err: "apply requires exactly 2 parameters",
698
    },
699
    RunProgramTest {
700
        prg: "(a (q . 1) (q . (100 200)))",
701
        args: "()",
702
        flags: 0,
703
        result: Some("(100 200)"),
704
        cost: 175,
705
        err: "",
706
    },
707
    RunProgramTest {
708
        prg: "(a (q . (+ 2 5)) (q . (20 30)))",
709
        args: "()",
710
        flags: 0,
711
        result: Some("50"),
712
        cost: 987,
713
        err: "",
714
    },
715
    RunProgramTest {
716
        prg: "((c (q . (+ (q . 50) 1)) (q . 500)))",
717
        args: "()",
718
        flags: 0,
719
        result: None,
720
        cost: 0,
721
        err: "in ((X)...) syntax X must be lone atom",
722
    },
723
    RunProgramTest {
724
        prg: "((#c) (q . 3) (q . 4))",
725
        args: "()",
726
        flags: 0,
727
        result: Some("((1 . 3) 1 . 4)"),
728
        cost: 140,
729
        err: "",
730
    },
731
    RunProgramTest {
732
        prg: "(a (q . 2) (q . (3 4 5)))",
733
        args: "()",
734
        flags: 0,
735
        result: Some("3"),
736
        cost: 179,
737
        err: "",
738
    },
739

740
    // ## PATH LOOKUPS
741

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

919
    // ## SOFTFORK
920

921
    // the arguments to softfork are checked in mempool mode, but in consensus
922
    // mode, only the cost argument is
923
    RunProgramTest {
924
        prg: "(softfork (q . 979))",
925
        args: "()",
926
        flags: ENABLE_BLS_OPS,
927
        result: Some("()"),
928
        cost: 1000,
929
        err: "",
930
    },
931
    RunProgramTest {
932
        prg: "(softfork (q . 979))",
933
        args: "()",
934
        flags: NO_UNKNOWN_OPS,
935
        result: None,
936
        cost: 1000,
937
        err: "softfork takes exactly 4 arguments",
938
    },
939
    RunProgramTest {
940
        prg: "(softfork (q . 959) (q . 9))",
941
        args: "()",
942
        flags: ENABLE_BLS_OPS,
943
        result: Some("()"),
944
        cost: 1000,
945
        err: "",
946
    },
947
    RunProgramTest {
948
        prg: "(softfork (q . 959) (q . 9))",
949
        args: "()",
950
        flags: NO_UNKNOWN_OPS,
951
        result: None,
952
        cost: 1000,
953
        err: "softfork takes exactly 4 arguments",
954
    },
955
    RunProgramTest {
956
        prg: "(softfork (q . 939) (q . 9) (q x))",
957
        args: "()",
958
        flags: ENABLE_BLS_OPS,
959
        result: Some("()"),
960
        cost: 1000,
961
        err: "",
962
    },
963
    RunProgramTest {
964
        prg: "(softfork (q . 939) (q . 9) (q x))",
965
        args: "()",
966
        flags: NO_UNKNOWN_OPS,
967
        result: None,
968
        cost: 1000,
969
        err: "softfork takes exactly 4 arguments",
970
    },
971
    // this is a valid invocation, but we don't implement any extensions (yet)
972
    // so the extension specifier 0 is still unknown
973
    RunProgramTest {
974
        prg: "(softfork (q . 919) (q . 9) (q x) (q . ()))",
975
        args: "()",
976
        flags: ENABLE_BLS_OPS,
977
        result: Some("()"),
978
        cost: 1000,
979
        err: "",
980
    },
981
    // when parsing the cost argument, we ignore redundant leading zeroes
982
    RunProgramTest {
983
        prg: "(softfork (q . 0x00000397) (q . 9) (q x) (q . ()))",
984
        args: "()",
985
        flags: ENABLE_BLS_OPS,
986
        result: Some("()"),
987
        cost: 1000,
988
        err: "",
989
    },
990
    RunProgramTest {
991
        prg: "(softfork (q . 919) (q . 9) (q x) (q . ()))",
992
        args: "()",
993
        flags: NO_UNKNOWN_OPS,
994
        result: None,
995
        cost: 1000,
996
        err: "unknown softfork extension",
997
    },
998

999
    // this is a valid invocation, but we don't implement any extensions (yet)
1000
    RunProgramTest {
1001
        prg: "(softfork (q . 919) (q . 0x00ffffffff) (q x) (q . ()))",
1002
        args: "()",
1003
        flags: ENABLE_BLS_OPS,
1004
        result: Some("()"),
1005
        cost: 1000,
1006
        err: "",
1007
    },
1008
    RunProgramTest {
1009
        prg: "(softfork (q . 919) (q . 0x00ffffffff) (q x) (q . ()))",
1010
        args: "()",
1011
        flags: NO_UNKNOWN_OPS,
1012
        result: None,
1013
        cost: 1000,
1014
        err: "unknown softfork extension",
1015
    },
1016

1017
    // we don't allow negative "extension" parameters
1018
    RunProgramTest {
1019
        prg: "(softfork (q . 919) (q . -1) (q x) (q . ()))",
1020
        args: "()",
1021
        flags: ENABLE_BLS_OPS,
1022
        result: Some("()"),
1023
        cost: 1000,
1024
        err: "",
1025
    },
1026
    RunProgramTest {
1027
        prg: "(softfork (q . 919) (q . -1) (q x) (q . ()))",
1028
        args: "()",
1029
        flags: NO_UNKNOWN_OPS,
1030
        result: None,
1031
        cost: 1000,
1032
        err: "softfork requires positive int arg",
1033
    },
1034

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

1053
    // we don't allow pairs as extension specifier
1054
    RunProgramTest {
1055
        prg: "(softfork (q . 919) (q 1 2 3) (q x) (q . ()))",
1056
        args: "()",
1057
        flags: ENABLE_BLS_OPS,
1058
        result: Some("()"),
1059
        cost: 1000,
1060
        err: "",
1061
    },
1062
    RunProgramTest {
1063
        prg: "(softfork (q . 919) (q 1 2 3) (q x) (q . ()))",
1064
        args: "()",
1065
        flags: NO_UNKNOWN_OPS,
1066
        result: None,
1067
        cost: 1000,
1068
        err: "softfork requires int arg",
1069
    },
1070

1071
    // the cost value is checked in consensus mode as well
1072
    RunProgramTest {
1073
        prg: "(softfork (q . 1000))",
1074
        args: "()",
1075
        flags: ENABLE_BLS_OPS,
1076
        result: None,
1077
        cost: 1000,
1078
        err: "cost exceeded",
1079
    },
1080
    // the cost parameter is mandatory
1081
    RunProgramTest {
1082
        prg: "(softfork)",
1083
        args: "()",
1084
        flags: ENABLE_BLS_OPS,
1085
        result: None,
1086
        cost: 0,
1087
        err: "first of non-cons",
1088
    },
1089
    RunProgramTest {
1090
        prg: "(softfork (q . 0))",
1091
        args: "()",
1092
        flags: ENABLE_BLS_OPS,
1093
        result: None,
1094
        cost: 1000,
1095
        err: "cost must be > 0",
1096
    },
1097
    // negative costs are not allowed
1098
    RunProgramTest {
1099
        prg: "(softfork (q . -1))",
1100
        args: "()",
1101
        flags: ENABLE_BLS_OPS,
1102
        result: None,
1103
        cost: 1000,
1104
        err: "softfork requires positive int arg",
1105
    },
1106
    RunProgramTest {
1107
        prg: "(softfork (q 1 2 3))",
1108
        args: "()",
1109
        flags: ENABLE_BLS_OPS,
1110
        result: None,
1111
        cost: 1000,
1112
        err: "softfork requires int arg",
1113
    },
1114

1115
    // test mismatching cost
1116
    RunProgramTest {
1117
        prg: "(softfork (q . 160) (q . 0) (q . (q . 42)) (q . ()))",
1118
        args: "()",
1119
        flags: ENABLE_BLS_OPS,
1120
        result: Some("()"),
1121
        cost: 241,
1122
        err: "",
1123
    },
1124
    // the program under the softfork is restricted by the specified cost
1125
    RunProgramTest {
1126
        prg: "(softfork (q . 159) (q . 0) (q . (q . 42)) (q . ()))",
1127
        args: "()",
1128
        flags: ENABLE_BLS_OPS,
1129
        result: None,
1130
        cost: 241,
1131
        err: "cost exceeded",
1132
    },
1133
    // the cost specified on the softfork must match exactly the cost of
1134
    // executing the program
1135
    RunProgramTest {
1136
        prg: "(softfork (q . 161) (q . 0) (q . (q . 42)) (q . ()))",
1137
        args: "()",
1138
        flags: ENABLE_BLS_OPS,
1139
        result: None,
1140
        cost: 10000,
1141
        err: "softfork specified cost mismatch",
1142
    },
1143

1144
    // without the flag to enable the BLS extensions, it's an unknown extension
1145
    RunProgramTest {
1146
        prg: "(softfork (q . 161) (q . 0) (q . (q . 42)) (q . ()))",
1147
        args: "()",
1148
        flags: NO_UNKNOWN_OPS,
1149
        result: None,
1150
        cost: 10000,
1151
        err: "unknown softfork extension",
1152
    },
1153

1154
    // coinid extension
1155
    // make sure we can execute the coinid operator under softfork 0
1156
    // this program raises an exception if the computed coin ID matches the
1157
    // expected
1158
    RunProgramTest {
1159
        prg: "(softfork (q . 1432) (q . 0) (q a (i (= (coinid (q . 0x1234500000000000000000000000000000000000000000000000000000000000) (q . 0x6789abcdef000000000000000000000000000000000000000000000000000000) (q . 123456789)) (q . 0x69bfe81b052bfc6bd7f3fb9167fec61793175b897c16a35827f947d5cc98e4bc)) (q x) (q . 0)) (q . ())) (q . ()))",
1160
        args: "()",
1161
        flags: ENABLE_BLS_OPS,
1162
        result: None,
1163
        cost: 1513,
1164
        err: "klvm raise",
1165
    },
1166
    // also test the opposite. This program is the same as above but it raises
1167
    // if the coin ID is a mismatch
1168
    RunProgramTest {
1169
        prg: "(softfork (q . 1432) (q . 0) (q a (i (= (coinid (q . 0x1234500000000000000000000000000000000000000000000000000000000000) (q . 0x6789abcdef000000000000000000000000000000000000000000000000000000) (q . 123456789)) (q . 0x69bfe81b052bfc6bd7f3fb9167fec61793175b897c16a35827f947d5cc98e4bc)) (q . 0) (q x)) (q . ())) (q . ()))",
1170
        args: "()",
1171
        flags: ENABLE_BLS_OPS,
1172
        result: Some("()"),
1173
        cost: 1513,
1174
        err: "",
1175
    },
1176

1177
    // coinid operator after hardfork, where coinid is available outside the
1178
    // softfork guard.
1179
    RunProgramTest {
1180
        prg: "(coinid (q . 0x1234500000000000000000000000000000000000000000000000000000000000) (q . 0x6789abcdef000000000000000000000000000000000000000000000000000000) (q . 123456789))",
1181
        args: "()",
1182
        flags: ENABLE_BLS_OPS_OUTSIDE_GUARD,
1183
        result: Some("0x69bfe81b052bfc6bd7f3fb9167fec61793175b897c16a35827f947d5cc98e4bc"),
1184
        cost: 861,
1185
        err: "",
1186
    },
1187
    RunProgramTest {
1188
        prg: "(coinid (q . 0x1234500000000000000000000000000000000000000000000000000000000000) (q . 0x6789abcdef000000000000000000000000000000000000000000000000000000) (q . 0x000123456789))",
1189
        args: "()",
1190
        flags: ENABLE_BLS_OPS_OUTSIDE_GUARD,
1191
        result: None,
1192
        cost: 861,
1193
        err: "coinid: invalid amount (may not have redundant leading zero)",
1194
    },
1195
    // make sure the coinid operator is not available unless the flag is
1196
    // specified
1197
    RunProgramTest {
1198
        prg: "(coinid (q . 0x1234500000000000000000000000000000000000000000000000000000000000) (q . 0x6789abcdef000000000000000000000000000000000000000000000000000000) (q . 0x000123456789))",
1199
        args: "()",
1200
        flags: NO_UNKNOWN_OPS,
1201
        result: None,
1202
        cost: 861,
1203
        err: "unimplemented operator",
1204
    },
1205
];
1206

1207
#[cfg(test)]
1208
fn check(res: (NodePtr, &str)) -> NodePtr {
191✔
1209
    assert_eq!(res.1, "");
191✔
1210
    res.0
191✔
1211
}
191✔
1212

1213
#[test]
1214
fn test_run_program() {
1✔
1215
    use crate::chik_dialect::ChikDialect;
1216
    use crate::test_ops::node_eq;
1217

1218
    for t in TEST_CASES {
73✔
1219
        let mut allocator = Allocator::new();
72✔
1220

72✔
1221
        let program = check(parse_exp(&mut allocator, &t.prg));
72✔
1222
        let args = check(parse_exp(&mut allocator, &t.args));
72✔
1223
        let expected_result = &t.result.map(|v| check(parse_exp(&mut allocator, v)));
72✔
1224

72✔
1225
        let dialect = ChikDialect::new(t.flags);
72✔
1226
        println!("prg: {}", t.prg);
72✔
1227
        match run_program(&mut allocator, &dialect, program, args, t.cost) {
72✔
1228
            Ok(Reduction(cost, prg_result)) => {
47✔
1229
                assert!(node_eq(&allocator, prg_result, expected_result.unwrap()));
47✔
1230
                assert_eq!(cost, t.cost);
47✔
1231

1232
                // now, run the same program again but with the cost limit 1 too low, to
1233
                // ensure it fails with the correct error
1234
                let expected_cost_exceeded =
47✔
1235
                    run_program(&mut allocator, &dialect, program, args, t.cost - 1).unwrap_err();
47✔
1236
                assert_eq!(expected_cost_exceeded.1, "cost exceeded");
47✔
1237
            }
1238
            Err(err) => {
25✔
1239
                println!("FAILED: {}", err.1);
25✔
1240
                assert_eq!(err.1, t.err);
25✔
1241
                assert!(expected_result.is_none());
25✔
1242
            }
1243
        }
1244
    }
1245
}
1✔
1246

1247
#[cfg(feature = "counters")]
1248
#[test]
1249
fn test_counters() {
1250
    use crate::chik_dialect::ChikDialect;
1251

1252
    let mut a = Allocator::new();
1253

1254
    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))"));
1255
    let args = check(parse_exp(&mut a, "(5033 1000)"));
1256
    let cost = 15073165;
1257

1258
    let (counters, result) =
1259
        run_program_with_counters(&mut a, &ChikDialect::new(0), program, args, cost);
1260

1261
    assert_eq!(counters.val_stack_usage, 3015);
1262
    assert_eq!(counters.env_stack_usage, 1005);
1263
    assert_eq!(counters.op_stack_usage, 3014);
1264
    assert_eq!(counters.atom_count, 2040);
1265
    assert_eq!(counters.pair_count, 22077);
1266
    assert_eq!(counters.heap_size, 771884);
1267

1268
    assert_eq!(result.unwrap().0, cost);
1269
}
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