rubysol 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/Manifest.txt +13 -0
- data/README.md +170 -0
- data/Rakefile +33 -0
- data/lib/rubysol/abi_proxy.rb +206 -0
- data/lib/rubysol/contract/crypto.rb +27 -0
- data/lib/rubysol/contract/runtime.rb +56 -0
- data/lib/rubysol/contract.rb +400 -0
- data/lib/rubysol/generator.rb +394 -0
- data/lib/rubysol/library.rb +126 -0
- data/lib/rubysol/runtime.rb +82 -0
- data/lib/rubysol/version.rb +23 -0
- data/lib/rubysol.rb +117 -0
- metadata +138 -0
@@ -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
|