Class: BSV::Script::Stack

Inherits:
Object
  • Object
show all
Defined in:
lib/bsv/script/interpreter/stack.rb

Overview

Script execution stack providing push/pop/peek operations for bytes, integers (ScriptNumber), and booleans.

Implements Forth-like stack manipulation operations (dup, drop, swap, rot, over, pick, roll, tuck) parameterised by count for the multi-element opcodes (OP_2DUP, OP_2SWAP, etc.).

A 32 MB aggregate memory cap is enforced on every push, matching the ts-sdk behaviour. This provides a natural bound for O(n²) opcodes such as OP_MUL operating on large script numbers.

Constant Summary collapse

MAX_MEMORY_BYTES =

Maximum total bytes held across all stack items (32 MB).

32 * 1024 * 1024

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeStack

Returns a new instance of Stack.



22
23
24
25
# File 'lib/bsv/script/interpreter/stack.rb', line 22

def initialize
  @items = []
  @memory_usage = 0
end

Class Method Details

.cast_bool(bytes) ⇒ Object

Bitcoin consensus boolean: false if empty, all-zero, or negative zero (0x80 last byte).



219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/bsv/script/interpreter/stack.rb', line 219

def self.cast_bool(bytes)
  return false if bytes.nil? || bytes.empty?

  bytes.each_byte.with_index do |byte, i|
    next if byte.zero?

    # Negative zero: last byte is exactly 0x80
    return !(i == bytes.bytesize - 1 && byte == 0x80)
  end

  false
end

Instance Method Details

#clearObject



105
106
107
108
# File 'lib/bsv/script/interpreter/stack.rb', line 105

def clear
  @items.clear
  @memory_usage = 0
end

#depthObject

— Info —



97
98
99
# File 'lib/bsv/script/interpreter/stack.rb', line 97

def depth
  @items.length
end

#drop_n(count) ⇒ Object

Remove the top N items.



130
131
132
133
134
135
136
137
# File 'lib/bsv/script/interpreter/stack.rb', line 130

def drop_n(count)
  check_count!(count, 'drop_n')
  stack_error!("stack too small for drop_n(#{count})") if @items.length < count

  removed = @items.pop(count)
  @memory_usage -= removed.sum(&:bytesize)
  nil
end

#dup_n(count) ⇒ Object

Duplicate the top N items.



117
118
119
120
121
122
123
124
125
126
127
# File 'lib/bsv/script/interpreter/stack.rb', line 117

def dup_n(count)
  check_count!(count, 'dup_n')
  stack_error!("stack too small for dup_n(#{count})") if @items.length < count

  start = @items.length - count
  added = @items[start..].sum(&:bytesize)
  check_memory!(added)
  copies = Array.new(count) { |i| @items[start + i].dup }
  @memory_usage += added
  @items.concat(copies)
end

#empty?Boolean

Returns:

  • (Boolean)


101
102
103
# File 'lib/bsv/script/interpreter/stack.rb', line 101

def empty?
  @items.empty?
end

#nip_n(idx) ⇒ Object

Remove item at offset idx from top (0 = top).



140
141
142
143
144
145
146
# File 'lib/bsv/script/interpreter/stack.rb', line 140

def nip_n(idx)
  check_index!(idx)

  removed = @items.delete_at(@items.length - 1 - idx)
  @memory_usage -= removed.bytesize
  removed
end

#over_n(count) ⇒ Object

Copy N items from 2N depth to top. OP_OVER (n=1): [x1 x2] -> [x1 x2 x1] OP_2OVER (n=2): [x1 x2 x3 x4] -> [x1 x2 x3 x4 x1 x2]



176
177
178
179
180
181
182
183
184
185
186
# File 'lib/bsv/script/interpreter/stack.rb', line 176

def over_n(count)
  check_count!(count, 'over_n')
  stack_error!("stack too small for over_n(#{count})") if @items.length < (2 * count)

  start = @items.length - (2 * count)
  added = @items[start, count].sum(&:bytesize)
  check_memory!(added)
  copies = Array.new(count) { |i| @items[start + i].dup }
  @memory_usage += added
  @items.concat(copies)
end

#peek_bool(idx = 0) ⇒ Object



91
92
93
# File 'lib/bsv/script/interpreter/stack.rb', line 91

def peek_bool(idx = 0)
  self.class.cast_bool(peek_bytes(idx))
end

#peek_bytes(idx = 0) ⇒ Object

— Peek —



73
74
75
76
77
# File 'lib/bsv/script/interpreter/stack.rb', line 73

def peek_bytes(idx = 0)
  check_index!(idx)

  @items[@items.length - 1 - idx]
end

#peek_int(idx = 0, max_length: ScriptNumber::MAX_BYTE_LENGTH, require_minimal: true) ⇒ Object

Decode a stack item as a BSV::Script::ScriptNumber without removing it.

Parameters:

  • idx (Integer) (defaults to: 0)

    depth from the top (0 = top).

  • max_length (Integer) (defaults to: ScriptNumber::MAX_BYTE_LENGTH)

    maximum allowed byte length. Defaults to BSV::Script::ScriptNumber::MAX_BYTE_LENGTH (750,000 bytes), matching Bitcoin’s nMaxNumSize constant.

  • require_minimal (Boolean) (defaults to: true)

    whether to enforce minimal encoding. Defaults to true — post-Genesis BSV requires minimal encoding.



87
88
89
# File 'lib/bsv/script/interpreter/stack.rb', line 87

def peek_int(idx = 0, max_length: ScriptNumber::MAX_BYTE_LENGTH, require_minimal: true)
  ScriptNumber.from_bytes(peek_bytes(idx), max_length: max_length, require_minimal: require_minimal)
end

#pick_n(idx) ⇒ Object

Copy item at index n to top (0 = top).



189
190
191
192
193
194
195
196
# File 'lib/bsv/script/interpreter/stack.rb', line 189

def pick_n(idx)
  check_index!(idx)

  copy = @items[@items.length - 1 - idx].dup
  check_memory!(copy.bytesize)
  @memory_usage += copy.bytesize
  @items.push(copy)
end

#pop_boolObject



67
68
69
# File 'lib/bsv/script/interpreter/stack.rb', line 67

def pop_bool
  self.class.cast_bool(pop_bytes)
end

#pop_bytesObject

— Pop —



46
47
48
49
50
51
52
# File 'lib/bsv/script/interpreter/stack.rb', line 46

def pop_bytes
  stack_error!('stack empty') if @items.empty?

  item = @items.pop
  @memory_usage -= item.bytesize
  item
end

#pop_int(max_length: ScriptNumber::MAX_BYTE_LENGTH, require_minimal: true) ⇒ Object

Decode the top stack item as a BSV::Script::ScriptNumber.

Parameters:

  • max_length (Integer) (defaults to: ScriptNumber::MAX_BYTE_LENGTH)

    maximum allowed byte length. Defaults to BSV::Script::ScriptNumber::MAX_BYTE_LENGTH (750,000 bytes), matching Bitcoin’s nMaxNumSize constant. Post-Genesis BSV makes this configurable at the node level; the SDK uses the standard default.

  • require_minimal (Boolean) (defaults to: true)

    whether to enforce minimal encoding. Defaults to true — post-Genesis BSV requires minimal encoding for script numbers.



63
64
65
# File 'lib/bsv/script/interpreter/stack.rb', line 63

def pop_int(max_length: ScriptNumber::MAX_BYTE_LENGTH, require_minimal: true)
  ScriptNumber.from_bytes(pop_bytes, max_length: max_length, require_minimal: require_minimal)
end

#push_bool(val) ⇒ Object



40
41
42
# File 'lib/bsv/script/interpreter/stack.rb', line 40

def push_bool(val)
  push_bytes(val ? "\x01".b : ''.b)
end

#push_bytes(data) ⇒ Object

— Push —



29
30
31
32
33
34
# File 'lib/bsv/script/interpreter/stack.rb', line 29

def push_bytes(data)
  check_memory!(data.bytesize)
  bytes = data.b
  @memory_usage += bytes.bytesize
  @items.push(bytes)
end

#push_int(script_number) ⇒ Object



36
37
38
# File 'lib/bsv/script/interpreter/stack.rb', line 36

def push_int(script_number)
  push_bytes(script_number.to_bytes)
end

#roll_n(idx) ⇒ Object

Move item at index n to top (0 = top). Memory usage is unchanged.



199
200
201
202
203
204
# File 'lib/bsv/script/interpreter/stack.rb', line 199

def roll_n(idx)
  check_index!(idx)

  val = @items.delete_at(@items.length - 1 - idx)
  @items.push(val)
end

#rot_n(count) ⇒ Object

Rotate: move the bottom N of the top 3N items to the top. OP_ROT (n=1): [x1 x2 x3] -> [x2 x3 x1] OP_2ROT (n=2): [x1 x2 x3 x4 x5 x6] -> [x3 x4 x5 x6 x1 x2]



151
152
153
154
155
156
157
# File 'lib/bsv/script/interpreter/stack.rb', line 151

def rot_n(count)
  check_count!(count, 'rot_n')
  stack_error!("stack too small for rot_n(#{count})") if @items.length < (3 * count)

  removed = @items.slice!(@items.length - (3 * count), count)
  @items.concat(removed)
end

#swap_n(count) ⇒ Object

Swap the top N items with the next N. OP_SWAP (n=1): [x1 x2] -> [x2 x1] OP_2SWAP (n=2): [x1 x2 x3 x4] -> [x3 x4 x1 x2]



162
163
164
165
166
167
168
169
170
171
# File 'lib/bsv/script/interpreter/stack.rb', line 162

def swap_n(count)
  check_count!(count, 'swap_n')
  stack_error!("stack too small for swap_n(#{count})") if @items.length < (2 * count)

  count.times do |i|
    a = @items.length - count + i
    b = @items.length - (2 * count) + i
    @items[a], @items[b] = @items[b], @items[a]
  end
end

#to_aObject



110
111
112
# File 'lib/bsv/script/interpreter/stack.rb', line 110

def to_a
  @items.dup
end

#tuckObject

Copy top and insert before second: [x1 x2] -> [x2 x1 x2]



207
208
209
210
211
212
213
214
# File 'lib/bsv/script/interpreter/stack.rb', line 207

def tuck
  stack_error!('stack too small for tuck') if @items.length < 2

  copy = @items.last.dup
  check_memory!(copy.bytesize)
  @memory_usage += copy.bytesize
  @items.insert(@items.length - 2, copy)
end