rubysol 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,394 @@
1
+
2
+
3
+ ##
4
+ # "centralize"
5
+ ## meta-programming magic (code generation) in module here - why? why not?
6
+ ## find a better name e.g Magic? Pilot? or CodeGen or Metagen or ???
7
+ module Generator
8
+
9
+
10
+ ############
11
+ ## helpers
12
+ def self._demodulize( path )
13
+ ## turn class.name into a symbol cutting off all namepspaces (::)
14
+ ## e.g. Contracts::ERC20 => ERC20
15
+ ##
16
+ ## note: ActiveSupport::String#demodulize is the same (for string)
17
+
18
+ path = path.to_s
19
+ if i = path.rindex('::')
20
+ path[(i+2)..-1]
21
+ else
22
+ path
23
+ end
24
+ end
25
+
26
+
27
+
28
+ module Function
29
+ ####
30
+ ## rename to pack_params/args or cook_params or typed_params or ???
31
+
32
+
33
+ def self.typecheck( type, value )
34
+ ## check if value is already typed?
35
+ if value.is_a?( Types::Typed )
36
+ ## type check
37
+ raise TypeError, "type #{type} expected; got #{value.pretty_print_inspect}" unless type == value.type
38
+ value
39
+ else
40
+ ## assume "literal" value
41
+ type.new( value )
42
+ end
43
+ end
44
+
45
+
46
+ def self.params( method, inputs, *args_unsafe, **kwargs_unsafe )
47
+ m = method
48
+ params = m.parameters
49
+ ## puts "params for #{m} - #{m.owner}:"
50
+ ## pp params
51
+
52
+ ## e.g.
53
+ ## [[:keyreq, :name],
54
+ ## [:keyreq, :symbol],
55
+ ## [:keyreq, :decimals],
56
+ ## [:keyreq, :totalSupply]]
57
+ ## get keys
58
+ keys = params.map { |param| param[1] }
59
+ ## puts "keys:"
60
+ ## pp keys
61
+
62
+ kwargs = if !args_unsafe.empty?
63
+ values = inputs.zip( args_unsafe ).map do |type, value|
64
+ typecheck( type, value )
65
+ end
66
+ ## puts "args:"
67
+ ## puts values.pretty_print_inspect
68
+
69
+ keys.zip( values ).map do |key,value|
70
+ [key,value]
71
+ end.to_h
72
+ elsif !kwargs_unsafe.empty?
73
+ types = keys.zip( inputs ).map do |key,type|
74
+ [key,type]
75
+ end.to_h
76
+ ## puts "types:"
77
+ ## pp types
78
+ kwargs_unsafe.map do |key,value|
79
+ type = types[key]
80
+ raise ArgumentError, "unknown kwarg #{key}; sorry" if type.nil?
81
+ [key, typecheck( type, value)]
82
+ end.to_h
83
+ else
84
+ ## assume no args - e.g. construct - double check for empty input spec/def!!!
85
+ if inputs.empty?
86
+ {}
87
+ else
88
+ raise ArgumentError, "Array (args) or Hash (kwargs) required for func call; sorry"
89
+ end
90
+ end
91
+
92
+ unless kwargs.empty?
93
+ puts "#{kwargs.size} kwarg(s):"
94
+ pp kwargs
95
+ end
96
+
97
+ kwargs
98
+ end
99
+ end # module Function
100
+
101
+
102
+ ## to rename to generate_typed or generate_wrapped or ??
103
+ ## typed_method??
104
+ def self.typed_function( contract_class, name, inputs: )
105
+ ## note: must? find matching method in class
106
+ ## todo/fix: use methods( false) if available (do NOT look-up in subclasses or such)
107
+
108
+ ## note: make sure name and contract_name is always a symbol
109
+ name = name.to_sym
110
+ contract_name = _demodulize( contract_class.name ).to_sym
111
+
112
+ exists = contract_class.instance_methods( false ).include?( name )
113
+ if !exists
114
+ error_message = "[ERRRO] no method #{name} found for sig in class #{contract_class.name}"
115
+ puts error_message
116
+ ## fix: change to NoMethodError - exists?
117
+ raise NameError, error_message
118
+ end
119
+
120
+ ## note: method lookup via method needs an object / INSTANCE
121
+ ## NOT working with class only!!!!
122
+ ## m = contract_class.method( name )
123
+ ## puts " bingo! #{name} - #{m.owner}"
124
+ puts " bingo! #{name}"
125
+
126
+
127
+ ## avoid recursive circle
128
+ ## exlcude method from method_add automagic wrapping/generation
129
+
130
+ contract_class.sigs_exclude << :"__#{contract_name}__#{name}_unsafe"
131
+ contract_class.sigs_exclude << :"__#{contract_name}__#{name}"
132
+ contract_class.sigs_exclude << name
133
+
134
+
135
+ ##
136
+ ## use :name_raw instead of :name_unsafe - why? why not?
137
+
138
+ ## rewire
139
+ ## alias_method :name_unsafe, :name
140
+ ## alias_method :name, :name_typed
141
+ contract_class.class_eval do
142
+
143
+ define_method :"__#{contract_name}__#{name}" do |*args_unsafe,**kwargs_unsafe|
144
+ puts "==> calling __#{contract_name}__#{name} (class #{contract_class.name})"
145
+
146
+ m = method( :"__#{contract_name}__#{name}_unsafe" )
147
+ kwargs = Function.params( m, inputs, *args_unsafe, **kwargs_unsafe )
148
+
149
+ ret = m.call( **kwargs )
150
+ ## todo/fix:
151
+ ## check returns type / value too
152
+ ret
153
+ end
154
+
155
+ ## note: must add class/contract name (via prefix) here!!
156
+ ## rename (unsafe/untyped) method
157
+ alias_method :"__#{contract_name}__#{name}_unsafe", name
158
+ alias_method name, :"__#{contract_name}__#{name}"
159
+ ## if name == :constructor ### add ERC20() or such
160
+ ## alias_method contract_name, :"__#{contract_name}__#{name}"
161
+ ## end
162
+ ## - use leading underscore - why? why not? e.g. _ERC20()
163
+ ## as alternative to NOT conflict with global conversion function - why? why not?
164
+ end
165
+ end # method typed_function
166
+
167
+
168
+ def self.typed_library_function( contract_class, name, inputs: )
169
+ ## note: must? find matching method in class
170
+ ## todo/fix: use methods( false) if available (do NOT look-up in subclasses or such)
171
+
172
+ ## todo - make sure contract_class is a module (not a class) !!!!
173
+
174
+ ## note: make sure name and contract_name is always a symbol
175
+ name = name.to_sym
176
+ contract_name = _demodulize( contract_class.name ).to_sym
177
+
178
+ exists = contract_class.instance_methods( false ).include?( name )
179
+ if !exists
180
+ error_message = "[ERRRO] no method #{name} found for sig in module #{contract_class.name}"
181
+ puts error_message
182
+ ## fix: change to NoMethodError - exists?
183
+ raise NameError, error_message
184
+ end
185
+
186
+ ## note: method lookup via method needs an object / INSTANCE
187
+ ## NOT working with class only!!!!
188
+ ## m = contract_class.method( name )
189
+ ## puts " bingo! #{name} - #{m.owner}"
190
+ puts " bingo! #{name}"
191
+
192
+
193
+ ## avoid recursive circle
194
+ ## exlcude method from method_add automagic wrapping/generation
195
+
196
+ contract_class.sigs_exclude << :"#{name}_unsafe"
197
+ contract_class.sigs_exclude << name
198
+
199
+ ##
200
+ ## use :name_raw instead of :name_unsafe - why? why not?
201
+
202
+ ## rewire
203
+ ## alias_method :name_unsafe, :name
204
+ ## alias_method :name, :name_typed
205
+ contract_class.class_eval do
206
+
207
+ alias_method :"#{name}_unsafe", name
208
+
209
+ define_method name do |*args_unsafe,**kwargs_unsafe|
210
+ puts "==> calling #{contract_name}.#{name} (module #{contract_class.name})"
211
+
212
+ m = method( :"#{name}_unsafe" )
213
+ kwargs = Function.params( m, inputs, *args_unsafe, **kwargs_unsafe )
214
+
215
+ ret = m.call( **kwargs )
216
+ ## todo/fix:
217
+ ## check returns type / value too
218
+ ret
219
+ end
220
+
221
+ ## make into module functions!!!
222
+ module_function name
223
+ module_function :"#{name}_unsafe"
224
+ end
225
+ end # method typed_library_function
226
+
227
+
228
+
229
+ ###
230
+ # add public getters helpers
231
+ def self.getter_function( contract_class, name, type )
232
+ ## note: make sure name is always a symbol
233
+ name = name.to_sym
234
+
235
+ if type.mapping?
236
+ mapping_getter_function( contract_class, name, type )
237
+ elsif type.array?
238
+ puts "[debug] auto-generate public array getter - #{name} : #{type}:"
239
+
240
+
241
+ ## auto-add/register sig(nature) in here - why? why not?
242
+ ## contract_class.sig( name,
243
+ ## [Types::Typed::UIntType.instance],
244
+ ## :view, returns: type.sub_type )
245
+
246
+ ## auto-add/register sig(nature) in here - why? why not?
247
+ ## fix-fix-fix - check if outputs is an array (or still single type?)
248
+ contract_class.sigs[ name ] = { inputs: [typeof(Types::UInt)],
249
+ outputs: [typeof(type.sub_type)],
250
+ options: [:view,:public] }
251
+ contract_class.sigs_exclude << name
252
+
253
+
254
+ contract_class.class_eval do
255
+ ## note: hack: must use kwargs for now!!! index: (not index) for now
256
+ define_method name do |index:|
257
+ puts "[debug] call public (state) array getter for #{name} : #{type} with index #{index} (#{contract_class.name})"
258
+ puts "[debug] self -> #{self}"
259
+ value = instance_variable_get( :"@#{name}" )
260
+ value[index]
261
+ end
262
+ end # class_evel
263
+
264
+ ## auto-add typed wrapper!!!!!
265
+ typed_function( contract_class, name, inputs: [typeof(Types::UInt)] )
266
+ else
267
+ puts "[debug] auto-generate public getter - #{name} : #{type}:"
268
+
269
+ ## auto-add/register sig(nature) in here - why? why not?
270
+ ## fix-fix-fix - check if outputs is an array (or still single type?)
271
+ contract_class.sigs[ name ] = { inputs: [],
272
+ outputs: [typeof(type)],
273
+ options: [:view, :public] }
274
+ contract_class.sigs_exclude << name
275
+
276
+
277
+ contract_class.class_eval do
278
+ define_method name do
279
+ puts "[debug] call public (state) getter for #{name} : #{type} (#{contract_class.name})"
280
+ puts "[debug] self -> #{self}"
281
+ value = instance_variable_get( :"@#{name}" )
282
+ value
283
+ end
284
+ end # class_eval
285
+
286
+ ## auto-add typed wrapper!!!!!
287
+ typed_function( contract_class, name, inputs: [] )
288
+ end # if
289
+
290
+ ## puts "after - instance_methods:"
291
+ ## pp contract_class.instance_methods( false )
292
+ end # method getter_function
293
+
294
+
295
+
296
+ def self.mapping_getter_function( contract_class, name, type )
297
+ ## note: make sure name is always a symbol
298
+ name = name.to_sym
299
+
300
+ index = 0
301
+ current_type = type
302
+
303
+ sig_args = []
304
+ while current_type.mapping? do
305
+ sig_args << current_type.key_type
306
+ current_type = current_type.value_type
307
+ index += 1
308
+ end
309
+
310
+
311
+ puts "[debug] auto-generate public mapping getter - #{name} : #{type} (#{contract_class.name}):"
312
+ puts "sig_args:"
313
+ pp sig_args
314
+ ## auto-add/register sig(nature) in here - why? why not?
315
+ ## contract_class.sig( name, sig_args, :view, returns: current_type)
316
+ puts " index: #{index}"
317
+ # {:arg0=>:address}
318
+ # index: 1
319
+
320
+ ## check - if sig_args is alreay array of types (or typedclasses???)
321
+ sig_args = sig_args.map {|sig_arg| typeof(sig_arg) }
322
+
323
+ contract_class.sigs[ name ] = { inputs: sig_args,
324
+ outputs: [typeof(current_type)],
325
+ options: [:view, :public] }
326
+ contract_class.sigs_exclude << name
327
+
328
+
329
+ puts contract_class.sigs[ name ].pretty_print_inspect
330
+
331
+
332
+ contract_class.class_eval do
333
+ ## note: hack: must use kwargs for now!!! arg0, arg1, arg2, for now
334
+ define_method name do |arg0:, arg1: nil, arg2: nil, arg3: nil|
335
+ puts "[debug] call public (state) mapping getter for #{name} : #{type} - index: #{index} (#{contract_class.name})"
336
+ puts "[debug] self -> #{self}"
337
+ args = [arg0, arg1, arg2, arg3]
338
+ puts "[debug] args -> #{args.pretty_print_inspect}"
339
+
340
+ ## raise ArgumentError, "expected #{index} argument(s); got #{args.size}" if args.size != index
341
+
342
+ value = instance_variable_get( "@#{name}" )
343
+ (0...index).each do |i|
344
+ value = value[ args[i] ]
345
+ end
346
+ value
347
+ end
348
+ end # class_eval
349
+
350
+ ## auto-add typed wrapper!!!!!
351
+ typed_function( contract_class, name, inputs: sig_args )
352
+
353
+ end # method mapping_getter_function
354
+
355
+
356
+
357
+ ##
358
+ # add global contract function to lookup AND typecheck
359
+ ## contracts using Contract.at
360
+ ## add via Module and Kernel - why? why not?
361
+ module Globals
362
+ end # module Globals
363
+
364
+ ## todo/check: move Kernel include to rubidity.rb script (with requires) - why? why not?
365
+ ::Kernel.include( Globals )
366
+
367
+ def self.global_function( contract_class )
368
+ ## todo/check: check if method exists already - why? why not?
369
+
370
+ contract_name = _demodulize( contract_class.name ).to_sym
371
+
372
+ ## use module_eval if exists? why? why not?
373
+ ## example:
374
+ ## def ERC20( address )
375
+ ## klass = ERC20
376
+ ## puts "==> calling #{klass.name}( #{address.pretty_print_inspect })"
377
+ ## obj = klass.at( address )
378
+ ## raise ArgumentError, "no #{klass.name} contract @ addreess #{address} found; sorry" if obj.nil?
379
+ ## puts " bingo! #{obj.class.name} (#{obj.class.parent_contracts}) contract found @ #{address}"
380
+ ## obj
381
+ ## end
382
+ Globals.class_eval do
383
+ define_method contract_name do |address|
384
+ klass = contract_class
385
+ puts "==> calling #{klass.name}( #{address.pretty_print_inspect })"
386
+ obj = klass.at( address )
387
+ raise ArgumentError, "no #{klass.name} contract @ address #{address} found; sorry" if obj.nil?
388
+ puts " bingo! #{obj.class.name} (#{obj.class.parent_contracts}) contract found @ #{address}"
389
+ obj
390
+ end
391
+ end
392
+ end
393
+ end # module Generator
394
+
@@ -0,0 +1,126 @@
1
+
2
+ module Library
3
+
4
+ def self.extended(base)
5
+ puts "==> turn (extend) module >#{base.name}< into library"
6
+ base.include( Types ) ## make solidity types availbe (Address, Bytes, etc.)
7
+ base.extend( ClassMethods )
8
+ ## todo/fix: add runtime and crypto methods too? why? why not?
9
+ end
10
+
11
+
12
+ module ClassMethods
13
+ def struct( class_name, **attributes )
14
+ typedclass = Types::Struct.new( class_name, scope: self, **attributes )
15
+ typedclass
16
+ end
17
+
18
+ def enum( class_name, *args )
19
+ typedclass = Types::Enum.new( class_name, *args, scope: self )
20
+ typedclass
21
+ end
22
+
23
+ ####
24
+ # note: sig machinery with method_added MUST come last here
25
+ def sigs
26
+ @sigs ||= {}
27
+ end
28
+
29
+ def sigs_unnamed ## unnamed sigs stack
30
+ @sigs_unnamed ||= []
31
+ end
32
+
33
+ ## ignore this methods;
34
+ ## do NOT (auto-)generate wrapper method popping (unnamed) sig from stack!!!
35
+ def sigs_exclude
36
+ ## note: always exclude globals
37
+ ## get or can get changed via runtime modules (simulacrm) and such!!!
38
+ ## e.g. msg.sender, block.timestamp, tx.origin, etc.
39
+ @sigs_exclude ||= [:block, :tx, :msg]
40
+ end
41
+
42
+
43
+
44
+ def sig( args=[], *options, returns: nil )
45
+
46
+ puts "[debug] add sig args: #{args.inspect}, options: #{options.inspect}, returns: #{returns.inspect}"
47
+ ## use inputs for args) and outputs for returns - why? why not?
48
+
49
+ ## check if include explicit visibility in options
50
+ if options.include?( :public ) ||
51
+ options.include?( :private ) ||
52
+ options.include?( :internal )
53
+ # do nothing / pass-along as is
54
+ else
55
+ # auto-add default up-front - :public or :internal if name starting with underscore (_)
56
+ visibility = name.start_with?( '_' ) ? :internal : :public
57
+ options.unshift( visibility )
58
+ end
59
+
60
+ ####
61
+ # auto-convert args (inputs), returns (outputs) to type (defs)
62
+ args = args.map { |value| typeof( value ) }
63
+
64
+ ###
65
+ ## note: turn returns into an array - empty if nil, etc.
66
+ ## always wrap into array
67
+ returns = if returns.nil?
68
+ []
69
+ elsif returns.is_a?( ::Array )
70
+ returns
71
+ else ## assume single type
72
+ [returns]
73
+ end
74
+
75
+ returns = returns.map { |value| typeof( value ) }
76
+
77
+
78
+ @sigs_unnamed ||= []
79
+ @sigs_unnamed.push( { inputs: args,
80
+ outputs: returns,
81
+ options: options } )
82
+ end
83
+
84
+
85
+
86
+ def method_added( name )
87
+
88
+ if sigs_exclude.include?( name )
89
+ puts "--- skip method added hook >#{name}< - found in sigs exclude"
90
+ return ## do nothing;
91
+ else
92
+ puts "==> method added hook >#{name}<... processing..."
93
+ end
94
+
95
+ ## pp name
96
+ ## pp name.class.name
97
+
98
+ name = name.to_sym ## note: make sure name is ALWAYS a symbol
99
+
100
+ ## note: method lookup via method needs an object / INSTANCE
101
+ ## NOT working with class only!!!!
102
+
103
+ # m = method( name )
104
+ # pp m.name
105
+ # pp m.parameters
106
+ # pp m
107
+
108
+ raise "no unnamed sig(nature) on stack for method >#{name}< in module >#{self.name}<; sorry" if sigs_unnamed.size == 0
109
+ sig_unnamed = sigs_unnamed.pop
110
+ puts " using sig_unnamed: #{sig_unnamed.inspect}"
111
+
112
+
113
+ @sigs ||= {}
114
+ raise "duplicate method sig(nature) for method >#{name}< in module >#{self.name}<; sorry" if @sigs.has_key?( name )
115
+ @sigs[ name ] = sig_unnamed
116
+
117
+
118
+
119
+ puts " generate typed_library_function >#{name}<"
120
+ Generator.typed_library_function( self, name,
121
+ inputs: sig_unnamed[ :inputs ] )
122
+
123
+ puts "<== method added hook >#{name}< done."
124
+ end
125
+ end # module ClassMethods
126
+ end # module Library
@@ -0,0 +1,82 @@
1
+ ## todo/ find a better name - why? why not?
2
+ ## change to Blockchain/Chain/Node/Client/Runner/Ctx/Context or ???
3
+ class Runtime
4
+
5
+
6
+ class Message
7
+ attr_reader :sender
8
+
9
+ def sender=(address)
10
+ ## note: allow literal and typed address
11
+ @sender = address.is_a?( Types::Address) ?
12
+ address : Types::Address.new( address )
13
+ end
14
+ end # class Message
15
+
16
+
17
+ class Tx
18
+ attr_reader :origin
19
+ def origin=(address)
20
+ @origin = address.is_a?( Types::Address ) ?
21
+ address : Types::Address.new( address )
22
+ end
23
+
24
+ def log_event( event )
25
+ puts "==> log_event:"
26
+ pp event
27
+ end
28
+ =begin
29
+ def log_event(event)
30
+ call_receipt.logs << event
31
+ event
32
+ end
33
+ =end
34
+ end # class Tx
35
+
36
+
37
+ class Block
38
+ attr_accessor :number, :timestamp
39
+
40
+ # def initialize(current_transaction)
41
+ # @current_transaction = current_transaction
42
+ # end
43
+
44
+ def blockhash( block_number )
45
+ ##
46
+ # unless @current_transaction.ethscription.block_number == block_number.value # TODO FIX
47
+ # raise "Not implemented"
48
+ # end
49
+ #
50
+ # @current_transaction.ethscription.block_blockhash
51
+
52
+ # todo - check what ("dummy") to return here??
53
+ end
54
+
55
+ def number=(number)
56
+ @number = number.is_a?( Types::UInt ) ?
57
+ number : Types::UInt.new( number )
58
+ end
59
+
60
+ def timestamp=(timestamp)
61
+ @timestamp = timestamp.is_a?( Types::Timestamp ) ?
62
+ timestamp : Types::Timestamp.new( timestamp )
63
+ end
64
+ end # class Block
65
+
66
+
67
+
68
+ def self.msg() @msg ||= Message.new; end
69
+ def self.block() @block ||= Block.new; end
70
+
71
+
72
+ ## use begin_transaction or
73
+ ## new_transaction or __ - why? why not?
74
+ def self.start_transaction() @tx = Tx.new; end
75
+ def self.current_transaction() @tx ||= Tx.new; end
76
+
77
+ class << self
78
+ alias_method :start_tx, :start_transaction
79
+ alias_method :current_tx, :current_transaction
80
+ end
81
+ end # class Runtime
82
+
@@ -0,0 +1,23 @@
1
+ module Rubysol
2
+ module Module
3
+ module Lang
4
+ MAJOR = 0
5
+ MINOR = 1
6
+ PATCH = 0
7
+ VERSION = [MAJOR,MINOR,PATCH].join('.')
8
+
9
+ def self.version
10
+ VERSION
11
+ end
12
+
13
+ def self.banner
14
+ "rubysol/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}] in (#{root})"
15
+ end
16
+
17
+ def self.root
18
+ File.expand_path( File.dirname(File.dirname(File.dirname(File.dirname(__FILE__)))) )
19
+ end
20
+
21
+ end # module Lang
22
+ end # module Module
23
+ end # module Rubysol