Class: TEALrb::Contract
- Inherits:
-
Object
- Object
- TEALrb::Contract
- 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
Class Attribute Summary collapse
-
.abi_interface ⇒ Object
Returns the value of attribute abi_interface.
-
.debug ⇒ Object
Returns the value of attribute debug.
-
.disable_abi_routing ⇒ Object
Returns the value of attribute disable_abi_routing.
-
.method_hashes ⇒ Object
Returns the value of attribute method_hashes.
-
.src_map ⇒ Object
Returns the value of attribute src_map.
-
.subroutines ⇒ Object
Returns the value of attribute subroutines.
-
.version ⇒ Object
Returns the value of attribute version.
Instance Attribute Summary collapse
-
#eval_location ⇒ Object
readonly
Returns the value of attribute eval_location.
-
#if_count ⇒ Object
Returns the value of attribute if_count.
-
#teal ⇒ Object
Returns the value of attribute teal.
Class Method Summary collapse
Instance Method Summary collapse
-
#abi_hash ⇒ Object
the hash of the abi description.
- #account(_account = nil) ⇒ Object (also: #accounts)
- #app(_app = nil) ⇒ Object (also: #apps)
- #app_args ⇒ Object
- #asset(_asset = nil) ⇒ Object (also: #assets)
- #box ⇒ Object
-
#comment(content, inline: false) ⇒ Object
insert comment into TEAL source.
-
#compile ⇒ Object
transpiles #main and routes abi methods.
-
#compile_string(string) ⇒ nil
transpiles the given string to TEAL.
- #compiled_program ⇒ Object
-
#define_subroutine(name, definition) ⇒ nil
defines a TEAL subroutine.
- #dump(directory = Dir.pwd, name: self.class.to_s.downcase, abi: true, src_map: true) ⇒ Object
- #formatted_teal ⇒ Object
- #generate_source_map(src) ⇒ Object
- #global(field = nil, *_args) ⇒ Object
- #global_opcode ⇒ Object
- #group_txn(_group_txn = nil) ⇒ Object (also: #group_txns)
-
#initialize ⇒ Contract
constructor
sets the ‘#pragma version`, defines teal methods, and defines subroutines.
- #inner_txn ⇒ Object
- #local ⇒ Object
- #logs ⇒ Object
- #main ⇒ Object
-
#placeholder(string) ⇒ Object
inserts a string into TEAL source.
-
#rb(input) ⇒ Object
return the input without transpiling to TEAL.
- #teal_source ⇒ Object
- #this_txn ⇒ Object
- #txn_type ⇒ Object
Methods included from ABI
Methods included from ByteOpcodes
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
#initialize ⇒ Contract
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_interface ⇒ Object
Returns the value of attribute abi_interface.
19 20 21 |
# File 'lib/tealrb/contract.rb', line 19 def abi_interface @abi_interface end |
.debug ⇒ Object
Returns the value of attribute debug.
19 20 21 |
# File 'lib/tealrb/contract.rb', line 19 def debug @debug end |
.disable_abi_routing ⇒ Object
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_hashes ⇒ Object
Returns the value of attribute method_hashes.
19 20 21 |
# File 'lib/tealrb/contract.rb', line 19 def method_hashes @method_hashes end |
.src_map ⇒ Object
Returns the value of attribute src_map.
19 20 21 |
# File 'lib/tealrb/contract.rb', line 19 def src_map @src_map end |
.subroutines ⇒ Object
Returns the value of attribute subroutines.
19 20 21 |
# File 'lib/tealrb/contract.rb', line 19 def subroutines @subroutines end |
.version ⇒ Object
Returns the value of attribute version.
19 20 21 |
# File 'lib/tealrb/contract.rb', line 19 def version @version end |
Instance Attribute Details
#eval_location ⇒ Object (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_count ⇒ Object
Returns the value of attribute if_count.
16 17 18 |
# File 'lib/tealrb/contract.rb', line 16 def if_count @if_count end |
#teal ⇒ Object
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}" = y..map(&:tag_name) next unless .include?('abi') || .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..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 .include? 'abi' end end |
Instance Method Details
#abi_hash ⇒ Object
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 account(_account = 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_args ⇒ Object
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 |
#comment(content, inline: false) ⇒ Object
insert comment into TEAL source
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 |
#compile ⇒ Object
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
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_program ⇒ Object
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
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_teal ⇒ Object
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_opcode ⇒ Object
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_txn ⇒ Object
154 155 156 |
# File 'lib/tealrb/contract.rb', line 154 def inner_txn InnerTxn.new self end |
#main ⇒ Object
397 398 399 |
# File 'lib/tealrb/contract.rb', line 397 def main nil end |
#placeholder(string) ⇒ Object
inserts a string into TEAL source
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_source ⇒ Object
110 111 112 |
# File 'lib/tealrb/contract.rb', line 110 def teal_source @teal.compact.join("\n") end |