rubysol 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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