universum 0.1.2 → 0.2.0

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