rubysol 0.1.0

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