Class: TEALrb::Contract

Inherits:
Object
  • Object
show all
Includes:
TEALrb, ABI, ByteOpcodes, Enums, MaybeOps, Opcodes::TEALOpcodes, Rewriters
Defined in:
lib/tealrb/contract.rb

Constant Summary collapse

VOID_OPS =
%w[assert err return app_global_put b bnz bz store
stores app_local_put app_global_del app_local_del callsub retsub
log itxn_submit itxn_next].freeze

Constants included from TEALrb

MAINNET

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ABI

#abi_return

Methods included from ByteOpcodes

#byte_b32, #byte_b64

Methods included from MaybeOps

#acct_has_balance?, #acct_param_value, #app_global_ex_exists?, #app_global_ex_value, #app_local_ex_exists?, #app_local_ex_value, #app_param_exists?, #app_param_value, #asset_holding_exists?, #asset_holding_value, #asset_param_exists?, #asset_param_value, #box_exists?, #box_len_exists?, #box_len_value, #box_value

Methods included from Opcodes::TEALOpcodes

#acct_params_get, #add, #addr, #addw, #app_global_del, #app_global_get, #app_global_get_ex, #app_global_put, #app_local_del, #app_local_get, #app_local_get_ex, #app_local_put, #app_opted_in, #app_params_get, #approve, #arg, #arg_0, #arg_1, #arg_2, #arg_3, #args, #assert, #asset_holding_get, #asset_params_get, #b, #balance, #base32, #base64_decode, #big_endian_add, #big_endian_divide, #big_endian_equal, #big_endian_less, #big_endian_less_eq, #big_endian_modulo, #big_endian_more, #big_endian_more_eq, #big_endian_multiply, #big_endian_not_equal, #big_endian_subtract, #bitlen, #bitwise_and, #bitwise_byte_invert, #bitwise_invert, #bitwise_or, #bitwise_xor, #bnz, #boolean_and, #boolean_or, #box_create, #box_del, #box_extract, #box_get, #box_len, #box_put, #box_replace, #bsqrt, #btoi, #bury, #byte, #bytec, #bytec_0, #bytec_1, #bytec_2, #bytec_3, #bytecblock, #bz, #bzero, #callsub, #concat, #cover, #dig, #divide, #divmodw, #divw, #dup, #dup2, #dupn, #ecdsa_pk_decompress, #ecdsa_pk_recover, #ecdsa_verify, #ed25519verify, #ed25519verify_bare, #equal, #err, #exp, #expw, #extract, #extract3, #extract_uint16, #extract_uint32, #extract_uint64, #frame_bury, #frame_dig, #gaid, #gaids, #getbit, #getbyte, #gitxn, #gitxna, #gitxnas, #gload, #gloads, #gloadss, #greater, #greater_eq, #gtxn, #gtxna, #gtxnas, #gtxns, #gtxnsa, #gtxnsas, #int, #intc, #intc_0, #intc_1, #intc_2, #intc_3, #intcblock, #itob, #itxn_begin, #itxn_field, #itxn_next, #itxn_submit, #itxna, #itxnas, #json_ref, #keccak256, #label, #len, #less, #less_eq, #load, #loads, #log, #match, #method_signature, #min_balance, #modulo, #multiply, #mulw, #not_equal, #padded_bitwise_and, #padded_bitwise_or, #padded_bitwise_xor, #pop, #popn, #proto, #pushbytes, #pushbytess, #pushint, #pushints, #replace, #replace2, #retsub, #select, #setbit, #setbyte, #sha256, #sha3_256, #sha512_256, #shl, #shr, #sqrt, #store, #stores, #substring, #substring3, #subtract, #swap, #switch, #teal_return, #txn, #txna, #txnas, #uncover, #vrf_verify, #zero?

Constructor Details

#initializeContract

sets the ‘#pragma version`, defines teal methods, and defines subroutines



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/tealrb/contract.rb', line 90

def initialize
  self.class.parse(self.class)

  @teal = TEAL.new ["#pragma version #{self.class.version}"], self
  @scratch = Scratch.new self

  @contract = self
  @if_count = 0

  self.class.method_hashes.each do |mh|
    define_subroutine(mh[:name], method(mh[:name]))
  end

  compile
end

Class Attribute Details

.abi_interfaceObject

Returns the value of attribute abi_interface.



19
20
21
# File 'lib/tealrb/contract.rb', line 19

def abi_interface
  @abi_interface
end

.debugObject

Returns the value of attribute debug.



19
20
21
# File 'lib/tealrb/contract.rb', line 19

def debug
  @debug
end

.disable_abi_routingObject

Returns the value of attribute disable_abi_routing.



19
20
21
# File 'lib/tealrb/contract.rb', line 19

def disable_abi_routing
  @disable_abi_routing
end

.method_hashesObject

Returns the value of attribute method_hashes.



19
20
21
# File 'lib/tealrb/contract.rb', line 19

def method_hashes
  @method_hashes
end

.src_mapObject

Returns the value of attribute src_map.



19
20
21
# File 'lib/tealrb/contract.rb', line 19

def src_map
  @src_map
end

.subroutinesObject

Returns the value of attribute subroutines.



19
20
21
# File 'lib/tealrb/contract.rb', line 19

def subroutines
  @subroutines
end

.versionObject

Returns the value of attribute version.



19
20
21
# File 'lib/tealrb/contract.rb', line 19

def version
  @version
end

Instance Attribute Details

#eval_locationObject (readonly)

Returns the value of attribute eval_location.



15
16
17
# File 'lib/tealrb/contract.rb', line 15

def eval_location
  @eval_location
end

#if_countObject

Returns the value of attribute if_count.



16
17
18
# File 'lib/tealrb/contract.rb', line 16

def if_count
  @if_count
end

#tealObject

Returns the value of attribute teal.



16
17
18
# File 'lib/tealrb/contract.rb', line 16

def teal
  @teal
end

Class Method Details

.inherited(klass) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
# File 'lib/tealrb/contract.rb', line 22

def inherited(klass)
  klass.version = 6
  klass.subroutines = {}
  klass.abi_interface = ABI::ABIDescription.new
  klass.abi_interface.name = klass.to_s
  klass.method_hashes = []
  klass.debug = false
  klass.disable_abi_routing = false
  klass.src_map = true
  super
end

.parse(klass) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/tealrb/contract.rb', line 34

def parse(klass)
  YARD::Tags::Library.define_tag('ABI Method', :abi)
  YARD::Tags::Library.define_tag('Subroutine', :subroutine)
  YARD::Tags::Library.define_tag('OnCompletes', :on_completion)
  YARD::Tags::Library.define_tag('Create', :create)

  YARD.parse Object.const_source_location(klass.to_s).first

  parsed_methods = {}
  YARD::Registry.all.each do |y|
    next unless y.type == :method
    next unless klass.instance_methods.include? y.name
    next if y.parent.type == :class && y.parent.to_s != klass.to_s

    if parsed_methods.keys.include? y.name
      raise "#{y.name} defined in two locations: \n  #{parsed_methods[y.name]}\n  #{y.file}:#{y.line}"
    end

    parsed_methods[y.name] = "#{y.file}:#{y.line}"

    tags = y.tags.map(&:tag_name)

    next unless tags.include?('abi') || tags.include?('subroutine')

    method_hash = { name: y.name.to_s, desc: y.base_docstring, args: [], returns: { type: 'void' },
                    on_completion: ['NoOp'], create: y.has_tag?('create') }

    y.tags.each do |t|
      method_hash[:returns] = { type: t.types&.first&.downcase } if t.tag_name == 'return'

      method_hash[:on_completion] = t.text[1..-2].split(',').map(&:strip) if t.tag_name == 'on_completion'
      next unless t.tag_name == 'param'

      method_hash[:args] << { name: t.name, type: t.types&.first&.downcase, desc: t.text }
    end

    klass.method_hashes << method_hash

    klass.abi_interface.add_method(**method_hash) if tags.include? 'abi'
  end
end

Instance Method Details

#abi_hashObject

the hash of the abi description



367
368
369
# File 'lib/tealrb/contract.rb', line 367

def abi_hash
  self.class.abi_interface.to_h
end

#account(_account = nil) ⇒ Object Also known as: accounts



114
115
116
# File 'lib/tealrb/contract.rb', line 114

def ( = nil)
  @account ||= Account.new self
end

#app(_app = nil) ⇒ Object Also known as: apps



120
121
122
# File 'lib/tealrb/contract.rb', line 120

def app(_app = nil)
  @app ||= App.new self
end

#app_argsObject



162
163
164
# File 'lib/tealrb/contract.rb', line 162

def app_args
  AppArgs.new self
end

#asset(_asset = nil) ⇒ Object Also known as: assets



126
127
128
# File 'lib/tealrb/contract.rb', line 126

def asset(_asset = nil)
  @asset ||= Asset.new self
end

#boxObject



150
151
152
# File 'lib/tealrb/contract.rb', line 150

def box
  Box.new self
end

#comment(content, inline: false) ⇒ Object

insert comment into TEAL source

Parameters:

  • content (String)

    content of the comment

  • inline (Boolean) (defaults to: false)

    whether the comment should be on the previous TEAL line



350
351
352
353
354
355
356
357
358
# File 'lib/tealrb/contract.rb', line 350

def comment(content, inline: false)
  content = " #{content}" unless content[0] == ' '
  if inline
    last_line = @teal.pop
    @teal << "#{last_line} //#{content}"
  else
    @teal << "//#{content}"
  end
end

#compileObject

transpiles #main and routes abi methods. To disable abi routing, set ‘@disable_abi_routing` to true in your Contract subclass



381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'lib/tealrb/contract.rb', line 381

def compile
  @teal << 'main:' if @teal.include? 'b main'
  route_abi_methods unless self.class.disable_abi_routing
  return unless respond_to? :main

  @eval_location = method(:main).source_location

  eval_tealrb(
    rewrite(
      method(:main).source,
      method_rewriter: true
    ),
    debug_context: 'main'
  )
end

#compile_string(string) ⇒ nil

transpiles the given string to TEAL

Parameters:

  • string (String)

    string to transpile

Returns:

  • (nil)


374
375
376
377
# File 'lib/tealrb/contract.rb', line 374

def compile_string(string)
  eval_tealrb(rewrite(string), debug_context: 'compile_string')
  nil
end

#compiled_programObject



401
402
403
404
405
406
407
# File 'lib/tealrb/contract.rb', line 401

def compiled_program
  compile_response = Algod.new.compile(formatted_teal)

  generate_source_map(formatted_teal) if compile_response.status != 200

  JSON.parse(compile_response.body)['result']
end

#define_subroutine(name, definition) ⇒ nil

defines a TEAL subroutine

Parameters:

  • name (Symbol)

    name of the method

  • definition (Lambda, Proc, UnboundMethod)

    the method definition

Returns:

  • (nil)


320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/tealrb/contract.rb', line 320

def define_subroutine(name, definition)
  return if method(name).source_location.first == __FILE__

  @eval_location = method(name).source_location

  define_singleton_method(name) do |*_args|
    callsub(name)
  end

  @teal << 'b main' unless @teal.include? 'b main'

  label(name) # add teal label

  comment_params = definition.parameters.map(&:last).join(', ')
  comment_content = "#{name}(#{comment_params})"
  comment(comment_content, inline: true)

  method_hash = self.class.method_hashes.find { _1[:name] == name.to_s }
  new_source = generate_subroutine_source(definition, method_hash)

  new_source = "#{new_source}retsub"

  eval_tealrb(new_source, debug_context: "subroutine: #{name}")

  nil
end

#dump(directory = Dir.pwd, name: self.class.to_s.downcase, abi: true, src_map: true) ⇒ Object



236
237
238
239
240
241
242
243
244
245
246
# File 'lib/tealrb/contract.rb', line 236

def dump(directory = Dir.pwd, name: self.class.to_s.downcase, abi: true, src_map: true)
  src = formatted_teal

  File.write(File.join(directory, "#{name}.teal"), src)
  File.write(File.join(directory, "#{name}.abi.json"), JSON.pretty_generate(abi_hash)) if abi

  return unless src_map

  src_map_json = JSON.pretty_generate(generate_source_map(src))
  File.write(File.join(directory, "#{name}.src_map.json"), src_map_json)
end

#formatted_tealObject



248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/tealrb/contract.rb', line 248

def formatted_teal
  new_lines = []
  comments = []

  @teal.compact.each do |ln|
    ln = ln.strip

    next if ln.empty?

    if ln[/^#pragma/]
      new_lines << { text: ln, void: true, comments: comments }
      comments = []
    elsif ln[%r{^//}]
      comments << ln
    else
      op = ln.split.first
      label_regex = %r{\S+:($| //)}

      label_type = label_number = label_end = nil

      if ln[label_regex]
        label_type = ln[/^if/] || ln[/^while/]
        label_number = ln[/(?<=^if)\d+/] || ln[/(?<=^while)\d+/]
        label_end = ln[/_end:/]
      end

      new_lines << { text: ln, void: VOID_OPS.include?(op), comments: comments, label: ln[label_regex],
                     label_type: label_type, label_number: label_number, label_end: label_end }
      comments = []
    end
  end

  output = []
  indent_level = 0
  current_labels = { 'while' => [], 'if' => [] }

  new_lines.each do |ln|
    indent_level = 1 if ln[:label] && indent_level.zero? && !ln[:label_type]

    if ln[:label_type]
      indent_level += 1 unless current_labels[ln[:label_type]].include?(ln[:label_number])
      current_labels[ln[:label_type]] << ln[:label_number]
    end

    ln_indent_level = indent_level
    ln_indent_level -= 1 if ln[:label]

    output << '' if !output.last&.empty? && (ln[:label] || ln[:comments].any?)

    ln[:comments].each { output << (("\t" * ln_indent_level) + _1) }
    output << (("\t" * ln_indent_level) + ln[:text])

    output << '' if ln[:void] && !output.last.empty?

    next unless ln[:label_end]

    indent_level -= 1
    current_labels[ln[:label_type]].delete ln[:label_number]
  end

  output.join("\n")
end

#generate_source_map(src) ⇒ Object



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/tealrb/contract.rb', line 174

def generate_source_map(src)
  last_location = nil
  src_map_hash = {}

  src.each_line.with_index do |ln, i|
    if ln[/src_map:/]
      last_location = ln[/(?<=src_map:)\S+/]
      next
    end

    src_map_hash[i + 1] ||= { location: last_location } if last_location
  end

  compile_response = Algod.new.compile(src)

  case compile_response.status
  when 400
    msg = JSON.parse(compile_response.body)['message']
    e_msg = StringIO.new
    e_msg.puts 'Error(s) while attempting to compile TEAL'
    msg.each_line do |ln|
      teal_line = ln.split(':').first.to_i
      ln_msg = ln.split(':')[1..].join(':').strip
      next if ln_msg == '"0"'

      if src_map_hash[teal_line]
        e_msg.puts "  #{teal_line} - #{src_map_hash[teal_line][:location]}: #{ln_msg}"
      else
        e_msg.puts "  #{ln}"
      end
    end
    raise e_msg.string
  when 200
    json_body = JSON.parse(compile_response.body)
    pc_mapping = json_body['sourcemap']['mapping']

    pc_array = pc_mapping.split(';').map do |v|
      SourceMap::VLQ.decode_array(v)[2]
    end

    last_line = 1

    line_to_pc = {}
    pc_to_line = {}
    pc_array.each_with_index do |line_delta, pc|
      last_line += line_delta.to_i

      next if last_line == 1

      line_to_pc[last_line] ||= []
      line_to_pc[last_line] << pc
      pc_to_line[pc] = last_line
    end

    line_to_pc.each do |line, pcs|
      src_map_hash[line][:pcs] = pcs if src_map_hash[line]
    end
  end

  src_map_hash
end

#global(field = nil, *_args) ⇒ Object



138
139
140
141
142
143
144
# File 'lib/tealrb/contract.rb', line 138

def global(field = nil, *_args)
  if field
    global_opcode(field)
  else
    @global ||= Global.new self
  end
end

#global_opcodeObject



13
# File 'lib/tealrb/contract.rb', line 13

alias global_opcode global

#group_txn(_group_txn = nil) ⇒ Object Also known as: group_txns



132
133
134
# File 'lib/tealrb/contract.rb', line 132

def group_txn(_group_txn = nil)
  @group_txn ||= GroupTxn.new self
end

#inner_txnObject



154
155
156
# File 'lib/tealrb/contract.rb', line 154

def inner_txn
  InnerTxn.new self
end

#localObject



166
167
168
# File 'lib/tealrb/contract.rb', line 166

def local
  Local.new self
end

#logsObject



158
159
160
# File 'lib/tealrb/contract.rb', line 158

def logs
  Logs.new self
end

#mainObject



397
398
399
# File 'lib/tealrb/contract.rb', line 397

def main
  nil
end

#placeholder(string) ⇒ Object

inserts a string into TEAL source

Parameters:

  • string (String)

    the string to insert



362
363
364
# File 'lib/tealrb/contract.rb', line 362

def placeholder(string)
  @teal << string
end

#rb(input) ⇒ Object

return the input without transpiling to TEAL



312
313
314
# File 'lib/tealrb/contract.rb', line 312

def rb(input)
  input
end

#teal_sourceObject



110
111
112
# File 'lib/tealrb/contract.rb', line 110

def teal_source
  @teal.compact.join("\n")
end

#this_txnObject



146
147
148
# File 'lib/tealrb/contract.rb', line 146

def this_txn
  ThisTxn.new self
end

#txn_typeObject



170
171
172
# File 'lib/tealrb/contract.rb', line 170

def txn_type
  TxnType.new self
end