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.
- 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
|