universum 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Manifest.txt +16 -0
- data/README.md +34 -35
- data/Rakefile +1 -1
- data/lib/universum.rb +19 -0
- data/lib/universum/account.rb +52 -0
- data/lib/universum/address.rb +75 -0
- data/lib/universum/contract.rb +87 -0
- data/lib/universum/function.rb +10 -0
- data/lib/universum/receipt.rb +52 -0
- data/lib/universum/safe_array.rb +69 -0
- data/lib/universum/safe_hash.rb +77 -0
- data/lib/universum/transaction.rb +89 -0
- data/lib/universum/type.rb +49 -0
- data/lib/universum/units_money.rb +64 -0
- data/lib/universum/units_time.rb +51 -0
- data/lib/universum/universum.rb +0 -394
- data/lib/universum/version.rb +2 -2
- data/test/contracts/greeter.rb +12 -13
- data/test/contracts/mytoken.rb +13 -14
- data/test/test_array.rb +52 -0
- data/test/test_greeter.rb +2 -2
- data/test/test_hash.rb +84 -0
- data/test/test_struct.rb +35 -0
- data/test/test_units_money.rb +60 -0
- data/test/test_units_time.rb +75 -0
- metadata +19 -3
@@ -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
|
data/lib/universum/universum.rb
CHANGED
@@ -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
|
|