universum 0.1.2 → 0.2.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,69 @@
1
+ # encoding: utf-8
2
+
3
+ class SafeArray
4
+
5
+ ## e.g.
6
+ ## Array.of( Address ), Array.of( Integer), etc.
7
+
8
+ def self.build_class( klass_value )
9
+ ## note: care for now only about value type / class
10
+
11
+ ## note: keep a class cache
12
+ cache = @@cache ||= {}
13
+ klass = cache[ klass_value ]
14
+ return klass if klass
15
+
16
+ klass = Class.new( SafeArray )
17
+ klass.class_eval( <<RUBY )
18
+ def self.klass_value
19
+ @klass_value ||= #{klass_value}
20
+ end
21
+ RUBY
22
+ ## add to cache for later (re)use
23
+ cache[ klass_value ] = klass
24
+ klass
25
+ end
26
+
27
+
28
+ def self.new_zero() new; end
29
+ def self.zero() @zero ||= new_zero; end
30
+
31
+
32
+
33
+ def initialize
34
+ ## todo/check: if array works if value is a (nested/multi-dimensional) array
35
+ @ary = []
36
+ end
37
+
38
+ def []=(index, value)
39
+ @ary[index] = value
40
+ end
41
+
42
+ def [](index)
43
+ item = @ary[ index ]
44
+ if item.nil?
45
+ ## todo/check:
46
+ ## always return (deep) frozen zero object - why? why not?
47
+ ## let user change the returned zero object - why? why not?
48
+ if self.class.klass_value.respond_to?( :new_zero )
49
+ ## note: use a new unfrozen copy of the zero object
50
+ ## changes to the object MUST be possible (new "empty" modifable object expected)
51
+ item = self.class.klass_value.new_zero
52
+ else # assume value semantics e.g. Integer, Bool, etc. zero values gets replaced
53
+ ## puts "use value semantics"
54
+ item = self.class.klass_value.zero
55
+ end
56
+ end
57
+ item
58
+ end
59
+
60
+ def push( item )
61
+ ## todo/fix: check if item.is_a? @type
62
+ ## note: Address might be a String too (Address | String)
63
+ ## store Address always as String!!! - why? why not?
64
+ @ary.push( item )
65
+ end
66
+
67
+ def size() @ary.size; end
68
+ def length() size; end
69
+ end # class SafeArray
@@ -0,0 +1,77 @@
1
+ # encoding: utf-8
2
+
3
+
4
+ class SafeHash
5
+
6
+ ## e.g.
7
+ ## Mapping.of( Address => Money )
8
+
9
+ ## note: need to create new class!! for every mapping
10
+ ## make klass_key class and
11
+ ## klass_value class into class instance variables
12
+ ## that can get used by zero
13
+ ## self.new returns a Hash.new/SafeHash.new like object
14
+
15
+ def self.build_class( klass_key, klass_value )
16
+ ## note: care for now only about value type / class
17
+
18
+ ## note: keep a class cache
19
+ cache = @@cache ||= {}
20
+ klass = cache[ klass_value ]
21
+ return klass if klass
22
+
23
+ klass = Class.new( SafeHash )
24
+ klass.class_eval( <<RUBY )
25
+ def self.klass_key
26
+ @klass_key ||= #{klass_key}
27
+ end
28
+ def self.klass_value
29
+ @klass_value ||= #{klass_value}
30
+ end
31
+ RUBY
32
+ ## add to cache for later (re)use
33
+ cache[ klass_value ] = klass
34
+ klass
35
+ end
36
+
37
+
38
+ def self.new_zero() new; end
39
+ def self.zero() @zero ||= new_zero; end
40
+
41
+
42
+ def initialize
43
+ ## todo/check: if hash works if value is a (nested) hash
44
+ @h = {}
45
+ end
46
+
47
+
48
+ def []=(key, value)
49
+ @h[key] = value
50
+ end
51
+
52
+ def [](key)
53
+ item = @h[ key ]
54
+ if item.nil?
55
+ ## pp self.class.klass_value
56
+ ## pp self.class.klass_value.zero
57
+
58
+ #####
59
+ # todo/check:
60
+ # add zero to hash on lookup (increases size/length)
61
+ # why? why not?
62
+
63
+ if self.class.klass_value.respond_to?( :new_zero )
64
+ ## note: use a dup(licated) unfrozen copy of the zero object
65
+ ## changes to the object MUST be possible (new "empty" modifable object expected)
66
+ item = @h[ key ] = self.class.klass_value.new_zero
67
+ else # assume value semantics e.g. Integer, Bool, etc. zero values gets replaced
68
+ ## puts "use value semantics"
69
+ item = @h[ key ] = self.class.klass_value.zero
70
+ end
71
+ end
72
+ item
73
+ end
74
+
75
+ def size() @h.size; end
76
+ def length() size; end
77
+ end # class SafeHash
@@ -0,0 +1,89 @@
1
+ # encoding: utf-8
2
+
3
+
4
+ class Transaction
5
+
6
+ def self.send( **kwargs ) ## convenience helper for Uni.send_transaction
7
+ Universum.send_transaction( **kwargs )
8
+ end
9
+
10
+
11
+ attr_reader :from, :to, :value, :data, :nonce
12
+
13
+ def initialize( from:, to:, value:, data:, nonce: nil )
14
+ ## note: from only allows accounts
15
+ if from.is_a?( Account )
16
+ account = from
17
+ else
18
+ account = Account.at( from ) ## lookup account by address
19
+ end
20
+
21
+ @from = account.address
22
+
23
+ if to.is_a?( Contract )
24
+ @to = "#{to.address} (#{to.class.name})"
25
+ elsif to.is_a?( Account ) ## note: to allows Contracts AND Accounts
26
+ @to = to.address
27
+ else
28
+ @to = to # might be a contract or account (pass through for now)
29
+ end
30
+
31
+ @value = value
32
+ @data = data
33
+
34
+ if nonce
35
+ @nonce = nonce
36
+ else
37
+ ## auto-add nonce (that is, tx counter - auto-increment)
38
+ @nonce = account.tx ## get transaction (tx) counter (starts with 0)
39
+ account._auto_inc_tx
40
+ end
41
+ end
42
+
43
+ def log_str
44
+ ## for debug add transaction (tx) args (e.g. from, value, etc.)
45
+ tx_args_str = ""
46
+ tx_args_str << "from: #{@from} ##{@nonce}"
47
+ tx_args_str << ", value: #{@value}" if @value > 0
48
+
49
+ if @to == '0x0000' ## special case - contract creation transaction
50
+ klass = @data[0] ## contract class - todo/fix: check if data[] is a contract class!!!
51
+ call_args = @data[1..-1] ## arguments
52
+
53
+ ## convert all args to string (with inspect) for debugging
54
+ ## check if pretty_inspect adds trailing newline? why? why not? possible?
55
+ call_args_str = call_args.reduce( [] ) { |ary,arg| ary; ary << arg.inspect }.join( ', ' )
56
+
57
+ "#{tx_args_str} => to: #{@to} create contract #{klass.name}.new( #{call_args_str} )"
58
+ else
59
+ if @data.empty? ## assume receive (default method) for now if data empty (no method specified)
60
+ "#{tx_args_str} => to: #{@to} call default fallback"
61
+ else
62
+ m = @data[0] ## method name / signature
63
+ call_args = @data[1..-1] ## arguments
64
+
65
+ ## convert all args to string (with inspect) for debugging
66
+ ## check if pretty_inspect adds trailing newline? why? why not? possible?
67
+ call_args_str = call_args.reduce( [] ) { |ary,arg| ary; ary << arg.inspect }.join( ', ' )
68
+
69
+ "#{tx_args_str} => to: #{@to} call #{m}( #{call_args_str} )"
70
+ end
71
+ end
72
+ end
73
+
74
+
75
+ def receipt
76
+ Receipt.find( self )
77
+ end
78
+
79
+ def contract # convenience helper (quick contract lookup)
80
+ rec = receipt
81
+ if rec
82
+ rec.contract
83
+ else
84
+ nil
85
+ end
86
+ end
87
+ end # class Transaction
88
+
89
+ Tx = Transaction ## add some convenience aliases (still undecided what's the most popular :-)
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+
4
+ ## add dummy bool class for mapping and (payable) method signature
5
+
6
+ class Integer
7
+ def self.zero() 0; end
8
+ end
9
+
10
+ class Bool
11
+ def self.zero() false; end
12
+ end
13
+
14
+ class Money
15
+ def self.zero() 0; end
16
+ end
17
+
18
+ class Void ## only used (reserved) for (payable) method signature now
19
+ end
20
+
21
+
22
+ class Mapping
23
+
24
+ def self.of( *args )
25
+ ## e.g. gets passed in [{Address=>Integer}]
26
+ ## check for Integer - use Hash.new(0)
27
+ ## check for Bool - use Hash.new(False)
28
+ if args[0].is_a? Hash
29
+ arg = args[0].to_a ## convert to array (for easier access)
30
+ klass_key = arg[0][0]
31
+ klass_value = arg[0][1]
32
+ klass = SafeHash.build_class( klass_key, klass_value )
33
+ klass.new
34
+ else
35
+ ## todo/fix: throw argument error/exception
36
+ Hash.new ## that is, "plain" {} with all "standard" defaults
37
+ end
38
+ end
39
+ end
40
+
41
+
42
+ class Array
43
+ ## "typed" safe array "constructor"
44
+ ## e.g. Array.of( Address ) or Array.of( Money ) or Array.of( Proposal, size: 2 ) etc.
45
+ def self.of( klass_value )
46
+ klass = SafeArray.build_class( klass_value )
47
+ klass.new ## todo: add klass.new( **kwargs ) for size: 2 etc.
48
+ end
49
+ end
@@ -0,0 +1,64 @@
1
+ # encoding: utf-8
2
+
3
+ class Integer
4
+
5
+ E3 = 10**3 # 1_000
6
+ E6 = 10**6 # 1_000_000
7
+ E9 = 10**9 # 1_000_000_000
8
+ E12 = 10**12 # 1_000_000_000_000
9
+ E15 = 10**15 # 1_000_000_000_000_000
10
+ E18 = 10**18 # 1_000_000_000_000_000_000
11
+
12
+ def e3() self * E3; end
13
+ def e6() self * E6; end
14
+ def e9() self * E9; end
15
+ def e12() self * E12; end
16
+ def e15() self * E15; end
17
+ def e18() self * E18; end
18
+
19
+
20
+ ## todo/fix: move ethereum money units to a module and include here
21
+
22
+ ###########
23
+ # Ethereum money units
24
+ #
25
+ # wei 1 wei | 1
26
+ # kwei (babbage) 1e3 wei | 1_000
27
+ # mwei (lovelace | ada) 1e6 wei | 1_000_000
28
+ # gwei (shannon) 1e9 wei | 1_000_000_000
29
+ # microether (szabo) 1e12 wei | 1_000_000_000_000
30
+ # milliether (finney) 1e15 wei | 1_000_000_000_000_000
31
+ # ether 1e18 wei | 1_000_000_000_000_000_000
32
+ #
33
+ # Names in Honor:
34
+ # wei => Wei Dai
35
+ # lovelace | ada => Ada Lovelace (1815-1852)
36
+ # babbage => Charles Babbage (1791-1871)
37
+ # shannon => Claude Shannon (1916-2001)
38
+ # szabo => Nick Szabo
39
+ # finney => Hal Finney (1956-2014)
40
+
41
+ def wei() self; end
42
+ def kwei() self * E3; end
43
+ def mwei() self * E6; end
44
+ def gwei() self * E9; end
45
+ def microether() self * E12; end
46
+ def milliether() self * E15; end
47
+ def ether() self * E18; end
48
+
49
+ ########################################################
50
+ ## alias - use alias or alias_method - why? why not?
51
+ def babbage() kwei; end
52
+ def lovelace() mwei; end
53
+ def ada() mwei; end ## todo/check: in use, really? keep just lovelave- why? why not?
54
+ def shannon() gwei; end
55
+ def szabo() microether; end
56
+ def finney() milliether; end
57
+
58
+ def microeth() microether; end
59
+ def micro() microether; end
60
+ def millieth() milliether; end
61
+ def milli() milliether; end
62
+ def eth() ether; end
63
+
64
+ end # class Integer
@@ -0,0 +1,51 @@
1
+ # encoding: utf-8
2
+
3
+
4
+ class Integer
5
+
6
+ ######################################
7
+ ## note: there's NO month (for now)!!!
8
+ ## why? month might be 28,29,30,31 days
9
+ ## use days e.g. 30.days or 31.days etc.
10
+
11
+ HOUR_IN_SECONDS = 60 * 60 # 60 minutes * 60 seconds
12
+ DAY_IN_SECONDS = 24 * HOUR_IN_SECONDS # 24 hours * 60 * 60
13
+ WEEK_IN_SECONDS = 7 * DAY_IN_SECONDS # 7 days * 24 * 60 * 60
14
+ FORTNIGHT_IN_SECONDS = 14 * DAY_IN_SECONDS # 14 days * 24 * 60 * 60
15
+ YEAR_IN_SECONDS = 365 * DAY_IN_SECONDS # 365 days * 24 * 60 * 60
16
+
17
+ def second() self; end
18
+ def minute() self * 60; end
19
+ def hour() self * HOUR_IN_SECONDS; end
20
+ def day() self * DAY_IN_SECONDS; end
21
+ def week() self * WEEK_IN_SECONDS; end
22
+ def fortnight() self * FORTNIGHT_IN_SECONDS; end
23
+ def year() self * YEAR_IN_SECONDS; end
24
+
25
+ ########################################################
26
+ ## alias - use alias or alias_method - why? why not?
27
+ def seconds() second; end
28
+ def secs() second; end
29
+ def sec() second; end
30
+ def s() second; end
31
+
32
+ def minutes() minute; end
33
+ def mins() minute; end
34
+ def min() minute; end
35
+ def m() minute; end
36
+
37
+ def hours() hour; end
38
+ def h() hour; end
39
+
40
+ def days() day; end
41
+ def d() day; end
42
+
43
+ def weeks() week; end
44
+ def w() week; end
45
+
46
+ def fortnights() fortnight; end
47
+
48
+ def years() year; end
49
+ def y() year; end
50
+
51
+ end # class Integer
@@ -1,255 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
3
 
4
-
5
- def sha256( str )
6
- Digest::SHA256.hexdigest( str )
7
- end
8
-
9
-
10
-
11
- module Address
12
- def self.zero() '0x0000'; end
13
-
14
- def address
15
- if @address
16
- @address
17
- else
18
- if is_a? Contract
19
- ## fix/todo: use/lookup proper addr from contract
20
- ## construct address for now from object_id
21
- "0x#{(object_id << 1).to_s(16)}"
22
- else ## assume Account
23
- '0x0000'
24
- end
25
- end
26
- end # method address
27
-
28
- def transfer( value ) ## @payable @public
29
- ## todo/fix: throw exception if insufficient funds
30
- send( value ) # returns true/false
31
- end
32
-
33
- def send( value ) ## @payable @public
34
- ## todo/fix: assert value > 0
35
- ## todo/fix: add missing -= part in transfer!!!!
36
-
37
- ## use this (current contract) for debit (-) ammount
38
- this._sub( value ) # sub(tract) / debit from the sender (current contract)
39
- _add( value ) # add / credit to the recipient
40
- end
41
-
42
- def balance
43
- @balance ||= 0 ## return 0 if undefined
44
- end
45
-
46
- ### private (internal use only) methods - PLEASE do NOT use (use transfer/send)
47
- def _sub( value )
48
- @balance ||= 0 ## return 0 if undefined
49
- @balance -= value
50
- end
51
-
52
- def _add( value )
53
- @balance ||= 0 ## return 0 if undefined
54
- @balance += value
55
- end
56
- end # class Address
57
-
58
-
59
- ## add dummy bool class for mapping and (payable) method signature
60
-
61
- class Integer
62
- def self.zero() 0; end
63
- end
64
-
65
- class Bool
66
- def self.zero() false; end
67
- end
68
-
69
- class Money
70
- def self.zero() 0; end
71
- end
72
-
73
- class Void ## only used (reserved) for (payable) method signature now
74
- end
75
-
76
-
77
- class Mapping
78
- def self.of( *args )
79
- ## e.g. gets passed in [{Address=>Integer}]
80
- ## check for Integer - use Hash.new(0)
81
- ## check for Bool - use Hash.new(False)
82
- if args[0].is_a? Hash
83
- arg = args[0].to_a ## convert to array (for easier access)
84
- if arg[0][1] == Integer
85
- Hash.new( Integer.zero ) ## if key missing returns 0 and NOT nil (the default)
86
- elsif arg[0][1] == Money
87
- Hash.new( Money.zero ) ## if key missing returns 0 and NOT nil (the default)
88
- elsif arg[0][1] == Bool
89
- Hash.new( Bool.zero )
90
- elsif arg[0][1] == Address
91
- Hash.new( Address.zero )
92
- else ## assume "standard" defaults
93
- ## todo: issue warning about unknown type - why? why not?
94
- Hash.new
95
- end
96
- else
97
- Hash.new ## that is, "plain" {} with all "standard" defaults
98
- end
99
- end
100
- end
101
-
102
-
103
-
104
-
105
- class Array
106
- def self.of( *args )
107
- Array.new ## that is, "plain" [] with all "standard" defaults
108
- end
109
- end
110
-
111
-
112
- class String
113
- def transfer( value )
114
- ## check if self is an address
115
- if self.start_with?( '0x' )
116
- Account[self].transfer( value )
117
- else
118
- raise "(Auto-)Type Conversion from Address (Hex) String to Account Failed; Expected String Starting with 0x got #{self}; Contract Halted (Stopped)"
119
- end
120
- end
121
-
122
- def send( value )
123
- ## check if self is an address
124
- if self.start_with?( '0x' )
125
- Account[self].send( value )
126
- else
127
- raise "(Auto-)Type Conversion from Address (Hex) String to Account Failed; Expected String Starting with 0x got #{self}; Contract Halted (Stopped)"
128
- end
129
- end
130
- end
131
-
132
-
133
- class Account
134
-
135
- @@directory = {}
136
- def self.find_by_address( key )
137
- ## clean key (allow "embedded" name e.g 0x1111 (Alice))
138
- key = key.gsub(/\(.+\)/, '' ).strip
139
- @@directory[ key ]
140
- end
141
- def self.find( key ) find_by_address( key ); end # make find_by_address the default finder
142
- def self.at( key) find_by_address( key ); end # another "classic" alias for find_by_address
143
-
144
- def self.[]( key )
145
- o = find_by_address( key )
146
- if o
147
- o
148
- else
149
- o = new( key )
150
- ## note: auto-register (new) address in (yellow page) directory
151
- @@directory[ key ] = o
152
- o
153
- end
154
- end
155
-
156
- def self.all() @@directory.values; end
157
-
158
-
159
- ####
160
- # account (builtin) services / transaction methods
161
- include Address ## includes address + send/transfer/balance
162
-
163
- ## note: for now allow write access too!!!
164
- def balance=( value )
165
- @balance = value
166
- end
167
-
168
- attr_reader :tx
169
- def _auto_inc_tx() @tx += 1; end ## "internal" method - (auto) increment transaction (tx) counter
170
-
171
- ## note: needed by transfer/send
172
- def this() Universum.this; end ## returns current contract
173
-
174
- private
175
- def initialize( address, balance: 0, tx: 0 )
176
- @address = address # type address - (hex) string starts with 0x
177
- @balance = balance # uint
178
- @tx = tx # transaction (tx) count (used for nonce and replay attack protection)
179
- end
180
-
181
- end # class Account
182
-
183
-
184
- class Contract
185
-
186
- @@directory = {}
187
- def self.find_by_address( key )
188
- ## clean key (allow "embedded" class name e.g 0x4de2ee8 (SatoshiDice))
189
- key = key.gsub(/\(.+\)/, '' ).strip
190
- @@directory[ key ];
191
- end
192
- def self.find( key ) find_by_address( key ); end # make find_by_address the default finder
193
- def self.at( key) find_by_address( key ); end # another "classic" alias for find_by_address
194
- def self.[]( key ) find_by_address( key ); end
195
-
196
- def self.store( key, o ) @@directory.store( key, o ); end ## store (add) new contract object (o) to hash / directory
197
- def self.all() @@directory.values; end
198
-
199
-
200
-
201
- ####
202
- # account (builtin) services / transaction methods
203
- include Address ## includes address + send/transfer/balance
204
-
205
- ## function sig(nature) macro for types (dummy for now)
206
- # e.g. use like
207
- # payable :process
208
- # payable :initialize
209
- # payable :bet, Integer
210
- # payable :lend_money, Address => Bool ## returns Bool
211
- def self.payable( *args ); end
212
-
213
- payable :receive
214
-
215
- ####
216
- # todo/double check: auto-add payable default fallback - why? why not?
217
- def receive ## @payable default fallback - use different name - why? why not? (e.g. handle/process/etc.)
218
- end
219
-
220
-
221
- def assert( condition )
222
- if condition == true
223
- ## do nothing
224
- else
225
- raise 'Contract Assertion Failed; Contract Halted (Stopped)'
226
- end
227
- end
228
-
229
-
230
- def this() Universum.this; end ## returns current contract
231
- def log( event ) Universum.log( event ); end
232
- def msg() Universum.msg; end
233
- def block() Universum.block; end
234
- def blockhash( number )
235
- ## todo/fix: only allow going back 255 blocks; check if number is in range!!!
236
- Universum.blockhash( number )
237
- end
238
-
239
- private
240
- def selfdestruct( owner ) ## todo/check: use a different name e.g. destruct/ delete - why? why not?
241
- ## selfdestruct function (for clean-up on blockchain)
242
- owner.send( @balance ) ## send back all funds owned/hold by contract
243
-
244
- ## fix: does nothing for now - add some code (e.g. cleanup)
245
- ## mark as destruct - why? why not?
246
- end
247
-
248
- end # class Contract
249
-
250
-
251
-
252
-
253
4
  ## blockchain message (msg) context
254
5
  ## includes: sender (address)
255
6
  ## todo: allow writable attribues e.g. sender - why? why not?
@@ -263,7 +14,6 @@ class Msg
263
14
  end # class Msg
264
15
 
265
16
 
266
-
267
17
  class Block
268
18
  attr_reader :timestamp, :number
269
19
 
@@ -274,150 +24,6 @@ class Block
274
24
  end # class Block
275
25
 
276
26
 
277
-
278
- class Transaction
279
-
280
- def self.send( **kwargs ) ## convenience helper for Uni.send_transaction
281
- Universum.send_transaction( **kwargs )
282
- end
283
-
284
-
285
- attr_reader :from, :to, :value, :data, :nonce
286
-
287
- def initialize( from:, to:, value:, data:, nonce: nil )
288
- ## note: from only allows accounts
289
- if from.is_a?( Account )
290
- account = from
291
- else
292
- account = Account.at( from ) ## lookup account by address
293
- end
294
-
295
- @from = account.address
296
-
297
- if to.is_a?( Contract )
298
- @to = "#{to.address} (#{to.class.name})"
299
- elsif to.is_a?( Account ) ## note: to allows Contracts AND Accounts
300
- @to = to.address
301
- else
302
- @to = to # might be a contract or account (pass through for now)
303
- end
304
-
305
- @value = value
306
- @data = data
307
-
308
- if nonce
309
- @nonce = nonce
310
- else
311
- ## auto-add nonce (that is, tx counter - auto-increment)
312
- @nonce = account.tx ## get transaction (tx) counter (starts with 0)
313
- account._auto_inc_tx
314
- end
315
- end
316
-
317
- def log_str
318
- ## for debug add transaction (tx) args (e.g. from, value, etc.)
319
- tx_args_str = ""
320
- tx_args_str << "from: #{@from} ##{@nonce}"
321
- tx_args_str << ", value: #{@value}" if @value > 0
322
-
323
- if @to == '0x0000' ## special case - contract creation transaction
324
- klass = @data[0] ## contract class - todo/fix: check if data[] is a contract class!!!
325
- call_args = @data[1..-1] ## arguments
326
-
327
- ## convert all args to string (with inspect) for debugging
328
- ## check if pretty_inspect adds trailing newline? why? why not? possible?
329
- call_args_str = call_args.reduce( [] ) { |ary,arg| ary; ary << arg.inspect }.join( ', ' )
330
-
331
- "#{tx_args_str} => to: #{@to} create contract #{klass.name}.new( #{call_args_str} )"
332
- else
333
- if @data.empty? ## assume receive (default method) for now if data empty (no method specified)
334
- "#{tx_args_str} => to: #{@to} call default fallback"
335
- else
336
- m = @data[0] ## method name / signature
337
- call_args = @data[1..-1] ## arguments
338
-
339
- ## convert all args to string (with inspect) for debugging
340
- ## check if pretty_inspect adds trailing newline? why? why not? possible?
341
- call_args_str = call_args.reduce( [] ) { |ary,arg| ary; ary << arg.inspect }.join( ', ' )
342
-
343
- "#{tx_args_str} => to: #{@to} call #{m}( #{call_args_str} )"
344
- end
345
- end
346
- end
347
-
348
-
349
- def receipt
350
- Receipt.find( self )
351
- end
352
-
353
- def contract # convenience helper (quick contract lookup)
354
- rec = receipt
355
- if rec
356
- rec.contract
357
- else
358
- nil
359
- end
360
- end
361
- end # class Transaction
362
-
363
- Tx = Transaction ## add some convenience aliases (still undecided what's the most popular :-)
364
-
365
-
366
-
367
-
368
- class Receipt ## transaction receipt
369
-
370
- @@directory = {}
371
- def self.find( tx )
372
- key = "#{tx.from}/#{tx.nonce}"
373
- @@directory[ key ];
374
- end
375
- def self.[]( tx ) find( tx ); end
376
-
377
- def self.store( o )
378
- key = "#{o.from}/#{o.nonce}"
379
- @@directory.store( key, o ); end ## store (add) new receipt object (o) to hash / directory
380
- def self.all() @@directory.values; end
381
-
382
-
383
- ## required attributes / fields
384
- attr_reader :nonce, :from, :to, :value,
385
- :block_number
386
- ## optional
387
- attr_reader :contract_address
388
-
389
- def initialize( tx:,
390
- block:,
391
- contract: nil )
392
- @nonce = tx.nonce
393
- @from = tx.from
394
- @to = tx.to
395
- @value = tx.value
396
- ## todo/fix: add data too!!!
397
-
398
- @block_number = block.number
399
- ## todo/fix: add block_hash
400
-
401
- if contract
402
- ## note: for easier debugging add class name in () to address (needs to get stripped away in lookup)
403
- @contract_address = "#{contract.address} (#{contract.class.name})"
404
- else
405
- @contract_address = nil
406
- end
407
- end
408
-
409
- def contract # convenience helper (quick contract lookup)
410
- if @contract_address
411
- Contract.find( @contract_address )
412
- else
413
- nil
414
- end
415
- end
416
- end
417
-
418
-
419
-
420
-
421
27
  class Universum ## Uni short for Universum
422
28
  ## convenience helpers
423
29