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,400 @@
1
+
2
+ class Contract
3
+
4
+
5
+ class << self
6
+ attr_accessor :state_variable_definitions,
7
+ :parent_contracts,
8
+ :events,
9
+ :is_abstract_contract
10
+ end
11
+
12
+
13
+ ##################
14
+ # parent contracts
15
+ # keep abstract - why? why not?
16
+ def self.abstract
17
+ @is_abstract_contract = true
18
+ end
19
+
20
+
21
+ def self.linearize_contracts( contract )
22
+ ## for now
23
+ ## include all classes before Contract
24
+ ## cache result - why? why not?
25
+
26
+ ##
27
+ ## todo/fix: check if include? Contract
28
+ ## if not raise error - CANNOT linearize (no contract base found)
29
+ classes = []
30
+ contract.ancestors.each do |ancestor|
31
+ break if ancestor == Contract
32
+ if ancestor.instance_of?( Class )
33
+ classes << ancestor
34
+ else ### assume Module
35
+ puts "[debug] skipping module - #{ancestor.name} : #{ancestor.class.name}"
36
+ end
37
+ end
38
+ classes
39
+ end
40
+
41
+ def self.linearized_parents
42
+ ## note: exclude self (that is, cut-off first class)
43
+ linearize_contracts( self )[1..-1]
44
+ end
45
+
46
+ class << self
47
+ ## note: for now the same (might change with support for module?)
48
+ alias_method :parent_contracts, :linearized_parents
49
+ end
50
+
51
+
52
+
53
+ ########
54
+ # state variables
55
+
56
+ def self.state_variable_definitions
57
+ @state_variable_definitions ||= {}
58
+ end
59
+
60
+
61
+ def self.define_state_variable(type, args)
62
+ ## note: REMOVE last item from array (use Array#pop)
63
+ ## make sure name is ALWAYS a symbol!!!
64
+ name = args.pop.to_sym
65
+
66
+ if state_variable_definitions[name]
67
+ raise "No shadowing: #{name} is already defined."
68
+ end
69
+
70
+ ## check for visibility - internal/private/public
71
+ ## note: make :public default and :internal only if name starting with underscore (_) - why? why not?
72
+ visibility = name.start_with?( '_' ) ? :internal : :public
73
+
74
+ # note: for now NO support for immutable and constant!!!!!
75
+ # immutable = false
76
+ # constant = false
77
+
78
+ ## todo/check - force strict check for double (public/private etc.) use - why? why not?
79
+ args.each do |arg|
80
+ case arg
81
+ when :public, :private, :internal then visibility = arg
82
+ # when :immutable then immutable = true
83
+ # when :constant then constant = true
84
+ else
85
+ raise ArgumentError, "unknown type qualifier >#{arg}<; sorry for typedef #{type} in #{args.inspect}"
86
+ end
87
+ end
88
+
89
+ state_variable_definitions[name] = { type: type,
90
+ visibility: visibility }
91
+ # immutable: immutable,
92
+ # constant: constant
93
+
94
+
95
+ ## check - visibility
96
+ if visibility == :public
97
+ contract_class = self
98
+ Generator.getter_function( contract_class, name, type )
99
+ end
100
+
101
+ type
102
+ end
103
+
104
+
105
+ def self.storage( **kwargs )
106
+ ## note: assume keys are names and values are types for storage
107
+ ## note: allow multiple calls of storage!!!
108
+
109
+ kwargs.each do |name, type|
110
+ type = typeof( type )
111
+
112
+ ## add support for more args - e.g. visibility or such - why? why not?
113
+ args = [name]
114
+ define_state_variable( type, args )
115
+ end
116
+ end
117
+
118
+
119
+
120
+ ####
121
+ # functions / abis
122
+
123
+ def self.abi
124
+ @abi ||= AbiProxy.new(self)
125
+ end
126
+
127
+ def self.public_abi() abi.public_api; end
128
+ def public_abi() self.class.public_abi; end
129
+
130
+
131
+
132
+
133
+ def self.struct( class_name, **attributes )
134
+ typedclass = Types::Struct.new( class_name, scope: self, **attributes )
135
+ typedclass
136
+ end
137
+
138
+ def self.enum( class_name, *args )
139
+ typedclass = Types::Enum.new( class_name, *args, scope: self )
140
+ typedclass
141
+ end
142
+
143
+ def self.event( class_name, **attributes )
144
+ typedclass = Types::Event.new( class_name, scope: self, **attributes )
145
+ typedclass
146
+ end
147
+
148
+
149
+ include CryptoHelper # e.g. keccak256
150
+ include RuntimeHelper # e.g. msg, tx, block, log, etc.
151
+
152
+
153
+ #####################
154
+ ## quick hack - add contract registry
155
+ ## for lookup by address and class
156
+ def self.registry() @@registry ||= {}; end
157
+ def self.register( obj )
158
+ registry[ obj.__address__ ] = [obj.class, obj] ## fix: in the future store state NOT object ref!!!
159
+ obj
160
+ end
161
+
162
+ def self.at( address )
163
+ klass = self
164
+ puts "==> calling #{klass.name}.at( #{address.pretty_print_inspect })"
165
+
166
+ ## note: support plain strings and typed address for now
167
+ ## use serialize to get "raw" string value of address
168
+ ## fix - fix - fix - change back to address?
169
+ addr_key = address.is_a?( Types::Address ) ? address.serialize : address
170
+ rec = registry[ addr_key ]
171
+
172
+ if rec
173
+ obj_klass = rec[0]
174
+ ## raise type error if not matching class type
175
+ if obj_klass == klass || obj_klass.parent_contracts.include?( klass )
176
+ obj = rec[1]
177
+ obj
178
+ else
179
+ raise TypeError, "#{obj_klass.name} contract found; is NOT a type of #{klass.name}; sorry"
180
+ end
181
+ else
182
+ nil # nothing found
183
+ end
184
+ end
185
+
186
+
187
+
188
+ ##########################################
189
+ ## "read-only" access for address
190
+ ## note: MUST use __address__ - why? why not?
191
+ ## otherwise will conflicts with global conversion function
192
+ ## when used in contract code e.g. address(0) or such
193
+
194
+ def __address__() @__address__; end
195
+
196
+ ## -- use a "number used once" counter for address generation for now
197
+ ## note: will count up for now for ALL contracts (uses @@)
198
+ ## fix: use a better formula later!!!!
199
+ def self.nonce() @@nonce ||= 0; end
200
+ def self.nonce=( value ) @@nonce = value; end
201
+
202
+ def __autoregister__
203
+ ## for now use
204
+ nonce = self.class.nonce += 1
205
+ @__address__ = '0x' + 'cc'*16 + ('%08d' % nonce ) ## count
206
+ puts " new #{self.class.name} contract @ address #{@__address__}"
207
+
208
+ self.class.register( self )
209
+ end
210
+
211
+ ## -fix-fix-fix
212
+ ## hack for update of msg.sender
213
+ ## make more generic and autoupate
214
+ # hack-y callstack to update msg.sender "by hand" for now
215
+ # fix-fix-fix - make it automagic somehow!!
216
+ def callstack( &block )
217
+ restore = Runtime.msg.sender
218
+ Runtime.msg.sender = @__address__
219
+ ret = block.call
220
+ Runtime.msg.sender = restore
221
+ ret
222
+ end
223
+
224
+
225
+
226
+
227
+ def initialize
228
+ unless self.class.abi.generated?
229
+ ## only generate once? double check
230
+ self.class.abi.generate_functions
231
+ end
232
+
233
+ ## rename to generate_storage or such - why? why not?
234
+ generate_state
235
+ end
236
+
237
+
238
+ def generate_state
239
+ puts "==> generate_state (ivars) #{self.class.name}"
240
+
241
+ self.class.state_variable_definitions.each do |name, definition|
242
+ type = definition[:type]
243
+
244
+ puts "[debug] add ivar @#{name} - #{type}"
245
+ ### note. create Typed instance here (via Type#new_zero)
246
+ instance_variable_set("@#{name}", type.new_zero )
247
+ end
248
+ end
249
+
250
+
251
+ def serialize
252
+ self.class.state_variable_definitions.keys.reduce({}) do |h, name|
253
+ ivar = instance_variable_get("@#{name}")
254
+ ## todo/fix: make sure ivar is_a? Typed!!!!
255
+ puts "WARN!! no ivar @#{name} found!!! - #{instance_variables}" if ivar.nil?
256
+ h[name] = ivar.serialize
257
+ h
258
+ end
259
+ end
260
+ alias_method :dump, :serialize ### use dump as alias - why? why not?
261
+
262
+
263
+ def deserialize(state_data)
264
+ state_data.each do |name, value|
265
+ ## todo/fix: make sure ivar is_a? Typed!!!!
266
+ ## lookup type info
267
+ definition = self.class.state_variable_definitions[ name ]
268
+ type = definition[:type]
269
+ typed = type.new( value ) ## note: always (re)create new typed classes (from literals)
270
+ instance_variable_set( "@#{name}", typed )
271
+ end
272
+ end
273
+ alias_method :load, :deserialize
274
+
275
+
276
+
277
+
278
+
279
+
280
+ ####
281
+ # note: sig machinery with method_added MUST come last here
282
+
283
+ def self.sigs
284
+ @sigs ||= {}
285
+ end
286
+
287
+ def self.sigs_unnamed ## unnamed sigs stack
288
+ @sigs_unnamed ||= []
289
+ end
290
+
291
+ ## ignore this methods;
292
+ ## do NOT (auto-)generate wrapper method popping (unnamed) sig from stack!!!
293
+ def self.sigs_exclude
294
+ ## note: always exclude globals
295
+ ## get or can get changed via runtime modules (simulacrm) and such!!!
296
+ ## e.g. msg.sender, block.timestamp, tx.origin, etc.
297
+ @sigs_exclude ||= [:block, :tx, :msg]
298
+ end
299
+
300
+
301
+
302
+ def self.sig( args=[], *options, returns: nil )
303
+
304
+ puts "[debug] add sig args: #{args.inspect}, options: #{options.inspect}, returns: #{returns.inspect}"
305
+ ## use inputs for args) and outputs for returns - why? why not?
306
+
307
+ ## check if include explicit visibility in options
308
+ if options.include?( :public ) ||
309
+ options.include?( :private ) ||
310
+ options.include?( :internal )
311
+ # do nothing / pass-along as is
312
+ else
313
+ # auto-add default up-front - :public or :internal if name starting with underscore (_)
314
+ visibility = name.start_with?( '_' ) ? :internal : :public
315
+ options.unshift( visibility )
316
+ end
317
+
318
+ ####
319
+ # auto-convert args (inputs), returns (outputs) to type (defs)
320
+ args = args.map { |value| typeof( value ) }
321
+
322
+ ###
323
+ ## note: turn returns into an array - empty if nil, etc.
324
+ ## always wrap into array
325
+ returns = if returns.nil?
326
+ []
327
+ elsif returns.is_a?( ::Array )
328
+ returns
329
+ else ## assume single type
330
+ [returns]
331
+ end
332
+
333
+ returns = returns.map { |value| typeof( value ) }
334
+
335
+
336
+ @sigs_unnamed ||= []
337
+ @sigs_unnamed.push( { inputs: args,
338
+ outputs: returns,
339
+ options: options } )
340
+ end
341
+
342
+
343
+
344
+ ##
345
+ # todo/fix:
346
+ # always auto-add (default) constructor
347
+ # CAN get redefined
348
+ # if not redefined
349
+ # is empty BUT will call super construct (if has parent)!!!!
350
+
351
+ ###
352
+ # sig []
353
+ # def constructor
354
+ # end
355
+ ## auto-add default constructor in Contract (base) here
356
+ ## add sig too - why? why not?
357
+ def constructor
358
+ puts " calling default (fallback dummy) contract constructor in #{self.class.name}"
359
+ end
360
+
361
+
362
+
363
+ def self.method_added( name )
364
+
365
+ if sigs_exclude.include?( name )
366
+ puts "--- skip method added hook >#{name}< - found in sigs exclude"
367
+ return ## do nothing;
368
+ else
369
+ puts "==> method added hook >#{name}<... processing..."
370
+ end
371
+
372
+ ## pp name
373
+ ## pp name.class.name
374
+
375
+ name = name.to_sym ## note: make sure name is ALWAYS a symbol
376
+
377
+ ## note: method lookup via method needs an object / INSTANCE
378
+ ## NOT working with class only!!!!
379
+
380
+ # m = method( name )
381
+ # pp m.name
382
+ # pp m.parameters
383
+ # pp m
384
+
385
+ raise "no unnamed sig(nature) on stack for method >#{name}< in class >#{self.name}<; sorry" if sigs_unnamed.size == 0
386
+ sig_unnamed = sigs_unnamed.pop
387
+ puts " using sig_unnamed: #{sig_unnamed.inspect}"
388
+
389
+
390
+ @sigs ||= {}
391
+ raise "duplicate method sig(nature) for method >#{name}< in class >#{self.name}<; sorry" if @sigs.has_key?( name )
392
+ @sigs[ name ] = sig_unnamed
393
+
394
+ puts " generate typed_function >#{name}<"
395
+ Generator.typed_function( self, name,
396
+ inputs: sig_unnamed[ :inputs ] )
397
+
398
+ puts "<== method added hook >#{name}< done."
399
+ end
400
+ end # class Contract